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 :

img01

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 :

img09

Le panneau

Le panneau des utilisateurs apparaît ainsi :

img10

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 :

img12

On peut sélectionner par exemple seulement les rédacteurs :

img13

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 :

img11

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 :

img14

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 :

img15

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 :

img16

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 :

img17

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 :

img44

Créer un utilisateur

L’administrateur peut créer un utilisateur en cliquant sur ce bouton :

img19

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 :

img20

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 :

img21

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 :

img22

Sinon on reçoit les messages d’erreur correspondants :

img23

Modifier un utilisateur

L’administrateur peut modifier les informations d’un utilisateur en cliquant sur ce bouton :

img24

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 :

img25

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 :

img45

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 :

img27

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 :

img28

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

img29

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 :

img30

Noms des rôles

Si l’administrateur veut modifier le nom des rôles il doit passer par le menu latéral :

img31

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 :

img32

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 :

img33

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 :

img34

On a ainsi fait le tous de la gestion des utilisateurs et de leurs rôles.

  1. 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 ?

    • Author 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 😀

      • Author 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 !

  2. 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
    ];

    • Author 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.

          • Author 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.

  3. Author 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 😉

  4. 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 ?

Laisser un commentaire