Créer une application : les utilisateurs
Article mis à jour le 29/10/2015
L’administrateur de l’application peut gérer les utilisateurs :
- consulter les paramètres
- modifier les paramètres (en particulier « Vu »)
- créer un utilisateur
- renommer les rôles
- supprimer un utilisateur
Cet article est destiné à décrire comment tout cela est géré dans l’application.
L’administrateur dispose d’un bloc sur son tableau de bord pour les utilisateurs :
Nous avons déjà vu le code pour les articles et les messages, je n’insiste donc pas.
L’administrateur dispose également d’un menu latéral pour les utilisateurs :
Le panneau
Le panneau des utilisateurs apparaît ainsi :
C’est la méthode index du contrôleur UserController qui l’affiche :
/** * Display a listing of the resource. * * @return Response */ public function index() { return $this->indexSort('total'); }
On voit qu’il est fait appel à une autre fonction du contrôleur :
/** * Display a listing of the resource. * * @param string $role * @return Response */ public function indexSort($role) { $counts = $this->user_gestion->counts(); $users = $this->user_gestion->index(4, $role); $links = $users->setPath('')->render(); $roles = $this->role_gestion->all(); return view('back.users.index', compact('users', 'links', 'counts', 'roles')); }
Cette méthode est utilisée pour l’affichage en filtrant le rôle. Dans notre cas le fait de passer « total » revient à afficher tous les utilisateurs. Pour filtrer les rôles on a ce panneau de boutons qui conduit directement à la fonction vue ci-dessus :
On peut sélectionner par exemple seulement les rédacteurs :
C’est la méthode index du repository UserRepository qui assure la gestion des données :
/** * Get users collection. * * @param int $n * @param string $role * @return Illuminate\Support\Collection */ public function index($n, $role) { if($role != 'total') { return $this->model ->with('role') ->whereHas('role', function($q) use($role) { $q->whereSlug($role); }) ->oldest('seen') ->latest() ->paginate($n); } return $this->model ->with('role') ->oldest('seen') ->latest() ->paginate($n); }
C’est cette vue qui est utilisée :
La partie du code qui gère le tableau est celui-ci :
<div class="table-responsive"> <table class="table"> <thead> <tr> <th>{{ trans('back/users.name') }}</th> <th>{{ trans('back/users.role') }}</th> <th>{{ trans('back/users.seen') }}</th> <th></th> <th></th> </tr> </thead> <tbody> @include('back.users.table') </tbody> </table> </div>
On voit qu’on inclut une autre vue (back/users/table.blade.php) :
@foreach ($users as $user) <tr {!! !$user->seen? 'class="warning"' : '' !!}> <td class="text-primary"><strong>{{ $user->username }}</strong></td> <td>{{ $user->role->title }}</td> <td>{!! Form::checkbox('seen', $user->id, $user->seen) !!}</td> <td>{!! link_to_route('user.show', trans('back/users.see'), [$user->id], ['class' => 'btn btn-success btn-block btn']) !!}</td> <td>{!! link_to_route('user.edit', trans('back/users.edit'), [$user->id], ['class' => 'btn btn-warning btn-block']) !!}</td> <td> {!! Form::open(['method' => 'DELETE', 'route' => ['user.destroy', $user->id]]) !!} {!! Form::destroy(trans('back/users.destroy'), trans('back/users.destroy-warning')) !!} {!! Form::close() !!} </td> </tr> @endforeach
Marquer « Vu »
Lorsqu’un utilisateur est nouvellement inscrit il apparaît comme non vu sur le panneau, avec la case décochée et le fond jauni pour attirer le regard :
Cette action est gérée en Javascript côté client avec envoi de la requête en Ajax :
// Seen gestion $(document).on('change', ':checkbox', function() { $(this).parents('tr').toggleClass('warning'); $(this).hide().parent().append('<i class="fa fa-refresh fa-spin"></i>'); var token = $('input[name="_token"]').val(); $.ajax({ url: '{!! url('userseen') !!}' + '/' + this.value, type: 'PUT', data: "seen=" + this.checked + "&_token=" + token }) .done(function() { $('.fa-spin').remove(); $('input[type="checkbox"]:hidden').show(); }) .fail(function() { $('.fa-spin').remove(); var chk = $('input[type="checkbox"]:hidden'); chk.show().prop('checked', chk.is(':checked') ? null:'checked').parents('tr').toggleClass('warning'); alert('{{ trans('back/users.fail') }}'); }); });
Avec une petite animation d’attente :
C’est la méthode updateSeen du contrôleur UserController qui est chargée de la requête :
/** * Update the specified resource in storage. * * @param Illuminate\Http\Request $request * @param App\Models\User $user * @return Response */ public function updateSeen( Request $request, $user) { $this->user_gestion->update($request->all(), $user); return response()->json(); }
Il est fait appel à la méthode update du repository UserRepository :
/** * Update a user. * * @param array $inputs * @param App\Models\User $user * @return void */ public function update($inputs, $user) { $user->confirmed = isset($inputs['confirmed']); $this->save($user, $inputs); }
Ce sera la même méthode pour modifier d’autre champs.
Notez qu’il est fait appel à la liaison du modèle au niveau des routes (route model binding). Regardez dans le service provider app/Providers/RouteServiceProvider :
/** * Define your route model bindings, pattern filters, etc. * * @param \Illuminate\Routing\Router $router * @return void */ public function boot(Router $router) { parent::boot($router); $router->model('user', 'App\Models\User'); }
Chaque fois qu’on utilise « user » comme paramètre de route Laravel crée automatiquement le modèle correspondant. C’est pour ça qu’on a directement le modèle comme paramètre dans la fonction updateSeen vue ci-dessus. Ca sera la même chose pour d’autres fonctions que nous allons voir plus loin.
Le contrôleur renvoie une réponse JSON et le Javascript agit en conséquence.
Voir une fiche
Pour consulter les informations d’un utilisateur l’administrateur clique sur ce bouton :
C’est la méthode show du contrôleur UserController qui est chargée de la requête :
/** * Display the specified resource. * * @param App\Models\User * @return Response */ public function show(User $user) { return view('back.users.show', compact('user')); }
Le contrôleur génère la vue :
Avec ce code :
@extends('back.template') @section('main') @include('back.partials.entete', ['title' => trans('back/users.dashboard'), 'icone' => 'user', 'fil' => link_to('user', trans('back/users.Users')) . ' / ' . trans('back/users.card')]) <p>{{ trans('back/users.name') . ' : ' . $user->username }}</p> <p>{{ trans('back/users.email') . ' : ' . $user->email }}</p> <p>{{ trans('back/users.role') . ' : ' . $user->role->title }}</p> @stop
Avec ce rendu :
Créer un utilisateur
L’administrateur peut créer un utilisateur en cliquant sur ce bouton :
C’est la méthode create du contrôleur UserController qui est chargée de la requête :
/** * Show the form for creating a new resource. * * @return Response */ public function create() { return view('back.users.create', $this->role_gestion->getAllSelect()); }
Il est fait appel à la méthode getAllSelect du repository RoleRepository :
/** * Get roles collection. * * @param App\Models\User * @return Array */ public function getAllSelect() { $select = $this->all()->lists('title', 'id'); return compact('select'); }
En effet on a besoin de la liste des rôles pour le formulaire.
Le contrôleur génère la vue :
Avec ce code :
@extends('back.template') @section('main') <!-- Entête de page --> @include('back.partials.entete', ['title' => trans('back/users.dashboard'), 'icone' => 'user', 'fil' => link_to('user', trans('back/users.Users')) . ' / ' . trans('back/users.creation')]) <div class="col-sm-12"> {!! Form::open(['url' => 'user', 'method' => 'post', 'class' => 'form-horizontal panel']) !!} {!! Form::control('text', 0, 'username', $errors, trans('back/users.name')) !!} {!! Form::control('email', 0, 'email', $errors, trans('back/users.email')) !!} {!! Form::control('password', 0, 'password', $errors, trans('back/users.password')) !!} {!! Form::control('password', 0, 'password_confirmation', $errors, trans('back/users.confirm-password')) !!} {!! Form::selection('role', $select, null, trans('back/users.role')) !!} {!! Form::submit(trans('front/form.send')) !!} {!! Form::close() !!} </div> @stop
Ce qui donne ce formulaire :
La méthode store du contrôleur UserController est chargée de gérer la soumission :
/** * Store a newly created resource in storage. * * @param App\requests\UserCreateRequest $request * * @return Response */ public function store( UserCreateRequest $request) { $this->user_gestion->store($request->all()); return redirect('user')->with('ok', trans('back/users.created')); }
La validation est assurée par la requête de formulaire UserCreateRequest :
<?php namespace App\Http\Requests; class UserCreateRequest extends Request { /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'username' => 'required|max:30|alpha|unique:users', 'email' => 'required|email|unique:users', 'password' => 'required|confirmed|min:8' ]; } }
Si la validation se passe bien le contrôleur appelle la méthode store du repository UserRepository pour la mise à jour dans la base :
/** * Create a user. * * @param array $inputs * @param int $confirmation_code * @return App\Models\User */ public function store($inputs, $confirmation_code = null) { $user = new $this->model; $user->password = bcrypt($inputs['password']); if($confirmation_code) { $user->confirmation_code = $confirmation_code; } else { $user->confirmed = true; } $this->save($user, $inputs); return $user; }
Puis le contrôleur affiche à nouveau la liste avec un message :
Sinon on reçoit les messages d’erreur correspondants :
Modifier un utilisateur
L’administrateur peut modifier les informations d’un utilisateur en cliquant sur ce bouton :
C’est la méthode edit du contrôleur UserController qui est chargée de la requête :
/** * Show the form for editing the specified resource. * * @param App\Models\User * @return Response */ public function edit(User $user) { return view('back.users.edit', array_merge(compact('user'), $this->role_gestion->getAllSelect())); }
Le contrôleur génère la vue :
Avec ce code :
@extends('back.template') @section('main') <!-- Entête de page --> @include('back.partials.entete', ['title' => trans('back/users.dashboard'), 'icone' => 'user', 'fil' => link_to('user', trans('back/users.Users')) . ' / ' . trans('back/users.edition')]) <div class="col-sm-12"> {!! Form::model($user, ['route' => ['user.update', $user->id], 'method' => 'put', 'class' => 'form-horizontal panel']) !!} {!! Form::control('text', 0, 'username', $errors, trans('back/users.name')) !!} {!! Form::control('email', 0, 'email', $errors, trans('back/users.email')) !!} {!! Form::selection('role', $select, $user->role_id, trans('back/users.role')) !!} {!! Form::submit(trans('front/form.send')) !!} {!! Form::close() !!} </div> @stop
Avec ce rendu :
Le même formulaire que celui de la création mais sans le mot de passe et avec une case à cocher pour la confirmation.
La méthode update du contrôleur UserController est chargée de gérer la soumission :
/** * Update the specified resource in storage. * * @param App\requests\UserUpdateRequest $request * @param App\Models\User * @return Response */ public function update( UserUpdateRequest $request, $user) { $this->user_gestion->update($request->all(), $user); return redirect('user')->with('ok', trans('back/users.updated')); }
La validation est assurée par la requête de formulaire UserUpdateRequest :
<?php namespace App\Http\Requests; class UserUpdateRequest extends Request { /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { $id = $this->user->id; return $rules = [ 'username' => 'required|max:30|alpha|unique:users,username,' . $id, 'email' => 'required|email|unique:users,email,' . $id ]; } }
Très proche de celle de la création mais sans le mot de passe et aussi en récupérant l’identifiant de l’utilisateur dans la requête pour la règle d’unicité du nom.
Si la validation se passe bien le contrôleur appelle la méthode update du repository UserRepository pour la mise à jour dans la base :
/** * Update a user. * * @param array $inputs * @param App\Models\User $user * @return void */ public function update($inputs, $user) { $user->confirmed = isset($inputs['confirmed']); $this->save($user, $inputs); }
Puis le contrôleur affiche à nouveau la liste avec un message :
Sinon on renvoie et on affiche les messages d’erreur de saisie.
Supprimer un utilisateur
L’administrateur peut modifier les informations d’un utilisateur en cliquant sur ce bouton :
C’est la méthode destroy du contrôleur UserController qui est chargée de la requête :
/** * Remove the specified resource from storage. * * @param App\Models\user $user * @return Response */ public function destroy(User $user) { $this->user_gestion->destroyUser($user); return redirect('user')->with('ok', trans('back/users.destroyed')); }
Côté client on affiche quand même un petit message en Javascript par sécurité :
Il est fait appel à la méthode destroyUser du repository UserRepository :
/** * Destroy a user. * * @param App\Models\User $user * @return void */ public function destroyUser(User $user) { $user->comments()->delete(); $user->delete(); }
On commence par supprimer les commentaires éventuels de l’utilisateur et ensuite on le supprime.
On affiche un message de confirmation pour l’administrateur :
Noms des rôles
Si l’administrateur veut modifier le nom des rôles il doit passer par le menu latéral :
C’est la méthode getRoles du contrôleur UserController qui est chargée de la requête :
/** * Display the roles form * * @return Response */ public function getRoles() { $roles = $this->role_gestion->all(); return view('back.users.roles', compact('roles')); }
Il est fait appel à la méthode all du repository RoleRepository :
/** * Get all roles. * * @return Illuminate\Support\Collection */ public function all() { return $this->role->all(); }
On récupère toutes les informations des rôles.
Le contrôleur génère la vue :
Avec ce code :
@extends('back.template') @section('main') @include('back.partials.entete', ['title' => trans('back/roles.dashboard'), 'icone' => 'user', 'fil' => link_to('user', trans('back/users.Users')) . ' / ' . trans('back/roles.roles')]) <div class="col-sm-12"> @if(session()->has('ok')) @include('partials/error', ['type' => 'success', 'message' => session('ok')]) @endif {!! Form::open(['url' => 'user/roles', 'method' => 'post', 'class' => 'form-horizontal panel']) !!} @foreach ($roles as $role) {!! Form::control('text', 0, $role->slug, $errors, trans('back/roles.' . $role->slug), $role->title) !!} @endforeach {!! Form::submit(trans('front/form.send')) !!} {!! Form::close() !!} </div> @stop
Et ce rendu :
On peut donc ici changer les noms.
La méthode postRoles du contrôleur UserController est chargée de gérer la soumission :
/** * Update roles * * @param App\requests\RoleRequest $request * @return Response */ public function postRoles(RoleRequest $request) { $this->role_gestion->update($request->except('_token')); return redirect('user/roles')->with('ok', trans('back/roles.ok')); }
La validation est assurée par la requête de formulaire RoleRequest :
<?php namespace App\Http\Requests; class RoleRequest extends Request { /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'admin' => 'required|alpha|max:50', 'redac' => 'required|alpha|max:50', 'user' => 'required|alpha|max:50' ]; } }
Si la validation se passe bien le contrôleur fait appel à la méthode update du repository RoleRepository :
/** * Update roles. * * @param array $inputs * @return void */ public function update($inputs) { foreach ($inputs as $key => $value) { $role = $this->role->where('slug', $key)->firstOrFail(); $role->title = $value; $role->save(); } }
On parcourt tous les rôles pour les mettre à jour.
Le contrôleur régénère la vue en la complétant avec un message :
On a ainsi fait le tous de la gestion des utilisateurs et de leurs rôles.
13 commentaires
antoinedavids
Bonsoir super tutoriel
Juste une question
c’est possible d’envoyé un email à l’utilisateur après création de compte
bestmomo
Salut,
La création d’un compte avec Laravel se passe dans le contrôleur RegisterController dans la méthode create, on peut donc envoyer un email à ce niveau.
ouhare
Concernant la pagination des utilisateurs filtrés par role.
D’après UserController.php (tiré de la version GitHub):
Premier point:
…
$links = $users->setPath( »)->render();
puis
‘links’ => str_replace(‘/sort/total’, », $links)
L’utilisation de str_replace est obsolète non ?
Deuxième point:
En utilisant setPath( »), lorsqu’on veut accéder à la page 2 de la liste des Redactor (par exemple), l’url ressemble à ‘/user?page=2’, du coup on obtient la page 2 de TOUS les utilisateurs.
J’ai contourné le problème en ajoutant : $links = $users->appends([‘role’ => $role])->setPath( »)->render();
ainsi les liens de pagination (hors ‘total’) sont de la forme: /user?page=2&role=’le role filtré’
puis:
public function index(Request $request) {
$role = ($request->has(‘role’)) ? $request->input(‘role’) : ‘total’;
return $this->indexGo($role);
}
et ça fonctionne !
Désolé si c’est un peu brouillon..
J’ai raté quelque chose dans votre implémentation ?
bestmomo
Bonjour,
Il faut que j’actualise cette série d’articles qui n’a pas suivi l’évolution du projet (https://github.com/bestmomo/laravel5-example) dans lequel entre autres la pagination a changé.
Mais il y a quand même effectivement un souci avec la pagination quand on choisit une catégorie d’utilisateurs.
ouhare
La solution dans mon commentaire précédent fonctionne parfaitement (pour ceux qui en ont besoin).
Merci encore pour cet exemple d’appli, elle m’a beaucoup aidé à comprendre ce fabuleux framework qu’est Laravel 😀
bestmomo
En fait ça ne fonctionne pas vraiment, je vais reprendre cette partie…
Edit : je l’ai reprise dans ce commit.
Attention les liens apparaissent en vert sur fond vert dans ce commentaire !
weip
Est-ce que tu peux expliquer la validation dans UserUpdateRequest?
Comment aller chercher l’utilisateur au complet et l’assigner dans les règles de validation avec des key différentes car moi ça ne fonctionne pas, ça génère une requête SQL étrange.
$id = $this->user;
return $rules = [
‘username’ => ‘required|max:30|alpha|unique:users,username,’ . $id,
’email’ => ‘required|email|unique:users,email,’ . $id
];
bestmomo
Bonjour,
La validation ici a pour seule particularité qu’il faut extraire l’id de l’utilisateur en cours de modification pour que la règle d’unicité soit désactivée pour lui. Quelle est la requête SQL étrange ?
weip
Mon $id = $this->user; retourne un objet json de mon utilisateur et non seulement son ID. Je comprends l’idée d’exclure l’ID en cours, mais ça semble me retourner l’objet au complet en json et donc ça brise ma requête SQL. Je n’ai pas la requête devant moi mais l’erreur donnait quelque chose de semblable :
SELECT … FROM User WHERE user_id ‘user_id: `4`, username: `weip`, email : `mon@email.com`’
weip
Par contre lorsque j’assigne la propriété id là ça fonctionne bien avec $id = $this->user->id et je crois que ça devrait fonctionner de cette manière. Est-ce que ton exemple fonctionne par convention? Je n’ai aucune idée pourquoi ça ne fonctionne pas de mon côté mais ce n’est pas la première fois que je vois passer l’objet au complet dans une validation.
bestmomo
Dans mon exemple « user » est un paramètre de la requête c’est pour ça que je peux récupérer l’id avec $this->user. Par contre dans un autre contexte $this->user va retourner un objet user de l’utilisateur connecté, ce qui semble être ton cas.
bestmomo
Salut,
Merci de ton intervention et bienvenu à Laravel ! J’ai voulu garder mon exemple sans intervention de package supplémentaire, mis à part celui pour le HTML qui me paraît indispensable. Je vais donc le garder ainsi mais tous les forks avec extensions sont les bienvenus !
Je ne sais pas encore comment je vais orienter la suite des articles, mais les colonnes sont ouvertes à d’autres contributions 😉
paguemaou
Super travail !
Après une année de galère a tenter d’apprendre Zend Framework, Doctrine, etc. , j’ai basculé sur Laravel. Quel bonheur !!! Simple, communauté dynamique, ressources web foisonnantes…. et des exemples réels comme celui-ci !
Sur mon site de test, j’ai enrichi le « Request » de vérification de création d’un user :
public function authorize()
{
// Accès réservé aux permission « admin »
if (Auth::check()) {
if (Auth::user()->can(‘perm_administrateur’)) // j’utilise Zizaco/Entrust
{
return true; // OK, continuer le Request
}
}
return false; // ne peut pas créer d’utilisateur, on bascule vers ForbiddenResponse
}
// Redirige vers une vue si authorize = false
public function forbiddenResponse()
{
// Optionally, send a custom response on authorize failure
// (default is to just redirect to initial page with errors)
//
// Can return a response, a view, a redirect, or whatever else
return response()->view(‘errors.403’);
}
Comme j’ai avancé sur certains points (intégration de Zizaco/Entrust, utilisation de JS Datatables), je peut te transmettre des docs à ce sujet, qui te permettront peut être de préparer d’autres (excellents) articles.
On mutualise ?