Tutoriel pour apprendre à créer une application Laravel

Les articles (back-end)

Dans le précédent tutoriel, on a vu comment sont gérés les articles et leurs commentaires au niveau du front-end. Nous allons à présent nous intéresser à la face cachée, non pas de la force, mais de l'application pour la gestion des articles par les administrateurs et les rédacteurs.

Si les administrateurs doivent avoir un accès total à tous les articles, commentaires et médias, les rédacteurs doivent être limités à leur propre production. Il ne serait pas judicieux de les laisser modifier l'article d'un autre rédacteur. Quant aux utilisateurs de base, ils ne doivent évidemment avoir accès à rien de tout cela.

Voyons comment cela est réalisé dans l'application.

9 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Le panneau

Sur son tableau de bord, l'administrateur trouve un bloc qui concerne les articles :

Image non disponible

Il peut ainsi savoir combien il y a d'articles et combien il y en a de nouveaux.

On a déjà vu que le tableau de bord est géré par la méthode admin du contrôleur AdminController :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
/**
* Show the admin panel.
*
* @param  App\Repositories\ContactRepository $contact_gestion
* @param  App\Repositories\BlogRepository $blog_gestion
* @param  App\Repositories\CommentRepository $comment_gestion
* @return Response
*/
public function admin(
    ContactRepository $contact_gestion, 
    BlogRepository $blog_gestion,
    CommentRepository $comment_gestion)
{   
    $nbrMessages = $contact_gestion->getNumber();
    $nbrUsers = $this->user_gestion->getNumber();
    $nbrPosts = $blog_gestion->getNumber();
    $nbrComments = $comment_gestion->getNumber();
 
    return view('back.index', compact('nbrMessages', 'nbrUsers', 'nbrPosts', 'nbrComments'));
}

Et que les quantités sont déterminées dans la méthode getNumber du repository de base BaseRepository :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
/**
 * Get number of records.
 *
 * @return array
 */
public function getNumber()
{
    $total = $this->model->count();
 
    $new = $this->model->whereSeen(0)->count();
 
    return compact('total', 'new');
}

Et que tout ça est envoyé dans la vue resources/views/back/index.blade.php.

Pour accéder à la gestion des articles, l'administrateur peut cliquer sur le bloc ou utiliser le menu latéral :

Image non disponible

Il dispose alors d'une vue globale sur les articles existants :

Image non disponible

Un rédacteur aboutit directement à cette page et ne dispose que de ses articles.

À ce niveau les actions possibles sont :

  • trier les articles par titre, date, publication, auteur et vu ;
  • publier ou dépublier un article ;
  • marquer un article vu ou non vu ;
  • voir un article ;
  • modifier un article ;
  • supprimer un article ;
  • créer un article.

Nous allons voir comment est traitée chacune de ces possibilités mise à part la visualisation d'un article qui se contente de le générer dans le front-end.

II. Le tri

Lorsque les articles ne sont pas triés, on a les deux petits sélecteurs qui apparaissent :

Image non disponible

Lorsqu'on clique sur les sélecteurs, on passe en tri décroissant et seul le sélecteur inférieur apparaît :

Image non disponible

Lorsqu'on clique à nouveau, on passe en mode tri croissant et seul le sélecteur supérieur apparaît :

Image non disponible

Ces actions sont gérées en Ajax pour ne pas avoir à recharger la page, mais juste le tableau.

Il y a évidemment un traitement en JavaScript pour gérer le visuel et s'occuper de la requête Ajax :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
// Sorting gestion
$('a.order').click(function(e) {
  e.preventDefault();
  // Sorting direction
  var sens;
  if($('span', this).hasClass('fa-unsorted')) sens = 'aucun';
  else if ($('span', this).hasClass('fa-sort-desc')) sens = 'desc';
  else if ($('span', this).hasClass('fa-sort-asc')) sens = 'asc';
  // Set to zero
  $('a.order span').removeClass().addClass('fa fa-fw fa-unsorted');
  // Adjust selected
  $('span', this).removeClass();
  var tri;
  if(sens == 'aucun' || sens == 'asc') {
    $('span', this).addClass('fa fa-fw fa-sort-desc');
    tri = 'desc';
  } else if(sens == 'desc') {
    $('span', this).addClass('fa fa-fw fa-sort-asc');
    tri = 'asc';
  }
  // Wait icon
  $('.breadcrumb li').append('<span id="tempo" class="fa fa-refresh fa-spin"></span>');       
  // Send ajax
  $.ajax({
    url: 'blog/order',
    type: 'GET',
    dataType: 'json',
    data: "name=" + $(this).attr('name') + "&sens=" + tri
  })
  .done(function(data) {
    $('tbody').html(data.view);
    $('.link').html(data.links);
    $('#tempo').remove();
  })
  .fail(function() {
    $('#tempo').remove();
    alert('{{ trans('back/blog.fail') }}');
  });
})

C'est la méthode indexOrder du contrôleur BlogController qui reçoit la requête :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
/**
 * Display a listing of the resource.
 *
 * @param  Illuminate\Http\Request $request
 * @return Response
 */
public function indexOrder(Request $request)
{
    $statut = $this->user_gestion->getStatut();
    $posts = $this->blog_gestion->index(
        10, 
        $statut == 'admin' ? null : $request->user()->id,
        $request->name,
        $request->sens
    );
     
    $links = $posts->appends([
            'name' => $request->name, 
            'sens' => $request->sens
        ]);
 
    if($request->ajax()) {
        return response()->json([
            'view' => view('back.blog.table', compact('statut', 'posts'))->render(), 
            'links' => $links->setPath('order')->render()
        ]);        
    }
 
    $links->setPath('')->render();
 
    $order = (object)[
        'name' => $request->name, 
        'sens' => 'sort-' . $request->sens            
    ];
 
    return view('back.blog.index', compact('posts', 'links', 'order'));
}

On a un middleware pour protéger cette méthode :

 
Sélectionnez
$this->middleware('redac', ['except' => ['indexFront', 'show', 'tag', 'search']]);

On veut que ce soit au moins un rédacteur (redac).

C'est la méthode index du repository BlogRepository qui gère au niveau de la base :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
/**
 * Get post collection.
 *
 * @param  int     $n
 * @param  int     $user_id
 * @param  string  $orderby
 * @param  string  $direction
 * @return Illuminate\Support\Collection
 */
public function index($n, $user_id = null, $orderby = 'created_at', $direction = 'desc')
{
    $query = $this->model
    ->select('posts.id', 'posts.created_at', 'title', 'posts.seen', 'active', 'user_id', 'slug', 'username')
    ->join('users', 'users.id', '=', 'posts.user_id')
    ->orderBy($orderby, $direction);
 
    if($user_id) 
    {
        $query->where('user_id', $user_id);
    } 
 
    return $query->paginate($n);
}

On transmet les paramètres nécessaires :

  • $n : nombre de pages pour la pagination ;
  • $user_id : identifiant si on a affaire à un rédacteur (pour sélectionner uniquement ses articles), sinon null ;
  • $orderby : champ de tri ;
  • $direction : direction de tri.

Le contrôleur retourne le code généré de la vue :

Image non disponible

En voici le code :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
@foreach ($posts as $post)
  <tr {!! !$post->seen && session('statut') == 'admin'? 'class="warning"' : '' !!}>
    <td class="text-primary"><strong>{{ $post->title }}</strong></td>
    <td>{{ $post->created_at }}</td> 
    <td>{!! Form::checkbox('active', $post->id, $post->active) !!}</td>
    @if(session('statut') == 'admin')
      <td>{{ $post->username }}</td>
      <td>{!! Form::checkbox('seen', $post->id, $post->seen) !!}</td>
    @endif
    <td>{!! link_to('blog/' . $post->slug, trans('back/blog.see'), ['class' => 'btn btn-success btn-block btn']) !!}</td>
    <td>{!! link_to_route('blog.edit', trans('back/blog.edit'), [$post->id], ['class' => 'btn btn-warning btn-block']) !!}</td>
    <td>
    {!! Form::open(['method' => 'DELETE', 'route' => ['blog.destroy', $post->id]]) !!}
      {!! Form::destroy(trans('back/blog.destroy'), trans('back/blog.destroy-warning')) !!}
    {!! Form::close() !!}
    </td>
  </tr>
@endforeach

On récupère donc côté client tout le HTML pour régénérer le tableau des articles. Dans le JavaScript :

 
Sélectionnez
1.
2.
3.
4.
5.
.done(function(data) {
  $('tbody').html(data.view);
  $('.link').html(data.links);
  $('#tempo').remove();
})

On remplace le HTML, on régénère les liens de pagination et on supprime la petite animation d'attente de résultat de l'action.

III. Publication et vu

L'administrateur ou le rédacteur dispose pour chaque article d'une case à cocher pour publier ou dépublier un article :

Image non disponible

Cette action est aussi gérée en Ajax et on a une petite animation en attendant qu'elle s'effectue :

Image non disponible

Voici le code JavaScript qui gère côté client :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
// Active gestion
$(document).on('change', ':checkbox[name="active"]', function() {
  $(this).hide().parent().append('<i class="fa fa-refresh fa-spin"></i>');
  var token = $('input[name="_token"]').val();
  $.ajax({
    url: '{{ url('postactive') }}' + '/' + this.value,
    type: 'PUT',
    data: "active=" + this.checked + "&_token=" + token
  })
  .done(function() {
    $('.fa-spin').remove();
    $('input:checkbox[name="active"]:hidden').show();
  })
  .fail(function() {
    $('.fa-spin').remove();
    chk = $('input:checkbox[name="active"]:hidden');
    chk.show().prop('checked', chk.is(':checked') ? null:'checked').parents('tr').toggleClass('warning');
    alert('{{ trans('back/blog.fail') }}');
  });
});

C'est la méthode updateActive du contrôleur BlogController qui reçoit la requête :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
/**
 * Update "active" for the specified resource in storage.
 *
 * @param  Illuminate\Http\Request $request
 * @param  int  $id
 * @return Response
 */
public function updateActive(
    Request $request, 
    $id)
{
    $this->blog_gestion->updateActive($request->all(), $id);
 
    return response()->json();
}

Cette méthode est protégée par middleware comme on l'a vu pour la précédente.

C'est la méthode updateActive du repository BlogRepository qui gère au niveau de la base :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
/**
 * Update "active" in post.
 *
 * @param  array  $inputs
 * @param  int    $id
 * @return void
 */
public function updateActive($inputs, $id)
{
    $post = $this->getById($id);
    $post->active = $inputs['active'] == 'true'; 
    $post->save();           
}

Le contrôleur renvoie une réponse JSON et le JavaScript agit selon la réussite ou l'échec.

Je ne détaille pas le fonctionnement pour les cases à cocher « Vu » puisque le code est quasiment identique à celui de la publication.

IV. Créer un article

IV-A. Le formulaire

Le formulaire de création d'un article est affiché par action sur le bouton « Ajouter un article » ou à partir du menu latéral :

Image non disponible
Image non disponible

C'est la méthode create du contrôleur BlogController qui reçoit la requête :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
/**
 * Show the form for creating a new resource.
 *
 * @return Response
 */
public function create()
{
    $url = config('medias.url');
     
    return view('back.blog.create')->with(compact('url'));
}

On voit qu'on va chercher l'URL pour le gestionnaire de médias dans la configuration.

Le contrôleur génère alors la vue :

Image non disponible

Si vous regardez le code de cette vue, vous n'allez pas trouver grand-chose :

 
Sélectionnez
1.
2.
3.
4.
5.
@extends('back.blog.template')
 
@section('form')
    {!! Form::open(['url' => 'blog', 'method' => 'post', 'class' => 'form-horizontal panel']) !!}  
@stop

En effet le reste du code est commun avec la vue pour la modification des articles. D'où la présence du template (resources/views/back/blog/template.blade.php) dont voici la partie du code qui génère le formulaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
<div class="col-sm-12">
    @yield('form')
 
    <div class="form-group checkbox pull-right">
        <label>
            {!! Form::checkbox('active') !!}
            {{ trans('back/blog.published') }}
        </label>
    </div>
 
    {!! Form::control('text', 0, 'title', $errors, trans('back/blog.title')) !!}
 
    <div class="form-group {!! $errors->has('slug') ? 'has-error' : '' !!}">
        {!! Form::label('slug', trans('back/blog.permalink'), ['class' => 'control-label']) !!}
        {!! url('/') . '/ ' . Form::text('slug', null, ['id' => 'permalien']) !!}
        <small class="text-danger">{!! $errors->first('slug') !!}</small>
    </div>
 
    {!! Form::control('textarea', 0, 'summary', $errors, trans('back/blog.summary')) !!}
    {!! Form::control('textarea', 0, 'content', $errors, trans('back/blog.content')) !!}
    {!! Form::control('text', 0, 'tags', $errors, trans('back/blog.tags'), isset($tags)? implode(',', $tags) : '') !!}
 
    {!! Form::submit(trans('front/form.send')) !!}
 
    {!! Form::close() !!}
</div>

Avec cet aspect :

Image non disponible

Le permalien est géré dynamiquement avec une fonction JavaScript :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
$("#title").keyup(function(){
        var str = sansAccent($(this).val());
        str = str.replace(/[^a-zA-Z0-9\s]/g,"");
        str = str.toLowerCase();
        str = str.replace(/\s/g,'-');
        $("#permalien").val(str);        
    });

Sont créées également deux zones de saisie (pour le sommaire et pour le contenu) avec CKEditor dont je vous ai déjà parlé dans un précédent tutoriel. J'ai évoqué dans le même tutoriel l'intégration de FileManager.

Enfin une zone permet la saisie des tags.

IV-B. La validation

La soumission du formulaire aboutit à la méthode store du contrôleur BlogController :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
/**
 * Store a newly created resource in storage.
 *
 * @param  App\Http\Requests\PostRequest $request
 * @return Response
 */
public function store(PostRequest $request)
{
    $this->blog_gestion->store($request->all(), $request->user()->id);
 
    return redirect('blog')->with('ok', trans('back/blog.stored'));
}

On a évidemment une validation assurée par la requête de formulaire PostRequest :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
<?php namespace App\Http\Requests;
 
use App\Models\Post;
 
class PostRequest extends Request {
 
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        $id = $this->blog ? ',' . $this->blog : '';
        return [
            'title' => 'required|max:255',
            'summary' => 'required|max:65000',
            'content' => 'required|max:65000',
            'slug' => 'required|unique:posts,slug' . $id,
            'tags' => 'tags'
        ];
    }
 
}

Les règles de validation sont classiques, mise à part celle qui concerne les tags. Il est fait appel à la règle « tags » qui n'est pas une règle de base de Laravel, mais constituée spécifiquement pour l'application.

Il y a une classe spéciale pour les règles de validation ajoutées :

Image non disponible

Elle ne comporte qu'une règle :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
<?php namespace app\Services;
 
use Illuminate\Validation\Validator;
 
class Validation extends Validator {
 
    public function validateTags($attribute, $value, $parameters)
    {
        return preg_match("/^[A-Za-z0-9-éèàù]{1,50}?(,[A-Za-z0-9-éèàù]{1,50})*$/", $value);
    }
 
}

Pour que cette classe soit connue de Laravel, on la déclare dans la méthode boot du provider AppServiceProvider :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
public function boot()
{
    Validator::resolver(function($translator, $data, $rules, $messages)
    {
        return new Validation($translator, $data, $rules, $messages);
    });
}

Le message est prévu dans resources/lang/fr et en/validation.php. Ainsi en cas de mauvaise saisie on obtient ce résultat :

Image non disponible

Le reste de la validation est classique.

Nota : il aurait été possible de prévoir l'expression régulière directement dans les règles de validation de la requête de formulaire, mais j'ai préféré montrer un exemple de réalisation de règle spécifique.

IV-C. L'enregistrement

Si la validation se passe bien le contrôleur fait appel à la méthode store du repository BlogRepository :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
/**
 * Create a post.
 *
 * @param  array  $inputs
 * @param  int    $user_id
 * @return void
 */
public function store($inputs, $user_id)
{
    $post = new $this->model;    
    $post = $this->savePost($post, $inputs, $user_id);
 
    // Tags gestion
    if(array_key_exists('tags',  $inputs) && $inputs['tags'] != '') {
 
        $tags = explode(',', $inputs['tags']);
 
        foreach ($tags as $tag) {
            $tag_ref = $this->tag->whereTag($tag)->first();
            if(is_null($tag_ref)) {
                $tag_ref = new $this->tag(); 
                $tag_ref->tag = $tag;
                $post->tags()->save($tag_ref);
            } else {
                $post->tags()->attach($tag_ref->id);
            }
        }
    }
}

Il est fait ici appel à une méthode commune avec la modification d'un article :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
/**
 * Create or update a post.
 *
 * @param  App\Models\Post $post
 * @param  array  $inputs
 * @param  bool   $user_id
 * @return App\Models\Post
 */
    private function savePost($post, $inputs, $user_id = null)
{   
    $post->title = $inputs['title'];
    $post->summary = $inputs['summary']; 
    $post->content = $inputs['content']; 
    $post->slug = $inputs['slug'];
    $post->active = isset($inputs['active']);    
    if($user_id) $post->user_id = $user_id;
 
    $post->save();
 
    return $post;
}

La gestion la plus délicate concerne évidemment les tags. Il faut faire une boucle pour les traiter tous, vérifier s'ils existent déjà dans la base, et enfin les attacher à l'article.

V. Modifier un article

La modification d'un article a évidemment beaucoup de code en commun avec la création d'un article.

V-A. Le formulaire

C'est cette fois la méthode edit du contrôleur BlogController qui est utilisée :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
/**
 * Show the form for editing the specified resource.
 *
 * @param  App\Repositories\UserRepository $user_gestion
 * @param  int  $id
 * @return Response
 */
public function edit(
    UserRepository $user_gestion, 
    $id)
{
    $post = $this->blog_gestion->getByIdWithTags($id);
 
    $this->authorize('change', $post);
 
    $url = config('medias.url');
 
    return view('back.blog.edit',  array_merge($this->blog_gestion->edit($post), compact('url')));
}

Il faut aller chercher les informations existantes (titre, sommaire, contenu, tags…) avec la méthode GetByIdWithTags du repository BlogRepository :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
/**
 * Get post collection.
 *
 * @param  int  $id
 * @return array
 */
public function GetByIdWithTags($id)
{
    return $this->model->with('tags')->findOrFail($id);
}

Remarquez la précaution prise pour limiter la modification d'un article à son créateur :

 
Sélectionnez
$this->authorize('change', $post);

On trouve la méthode correspondante dans app/Policies/PostPolicy.php :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
/**
 * Determine if the given post can be changed by the user.
 *
 * @param  \App\Models\User  $user
 * @param  \App\Models\Post  $post
 * @return bool
 */
public function change(User $user, Post $post)
{
    return $user->id === $post->user_id;
}

D'autre part un administrateur a tous les droits et peut aussi modifier un article même s'il ne lui appartient pas :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
/**
 * Grant all abilities to administrator.
 *
 * @param  \App\Models\User  $user
 * @param  string  $ability
 * @return bool
 */
public function before(User $user, $ability)
{
    if ($user->isAdmin()) {
        return true;
    }
}

Le contrôleur génère la vue :

Image non disponible

Avec aussi peu de code que pour la création :

 
Sélectionnez
1.
2.
3.
4.
5.
@extends('back.blog.template')
 
@section('form')
    {!! Form::model($post, ['route' => ['blog.update', $post->id], 'method' => 'put', 'class' => 'form-horizontal panel']) !!}
@stop

Cette fois, on a Form::model pour avoir nos champs de saisie automatiquement complétés par le modèle $post. Le formulaire est évidemment le même avec les champs complétés.

V-B. La validation

La validation est presque identique puisqu'elle utilise la même requête de formulaire que nous avons déjà vue ci-dessus (PostRequest). Comment est-ce qu'on fait la différence ? Tout simplement, en testant la présence du paramètre blog dans la requête :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
public function rules()
{
    $id = $this->blog ? ',' . $this->blog : '';
    return [
        'title' => 'required|max:255',
        'summary' => 'required|max:65000',
        'content' => 'required|max:65000',
        'slug' => 'required|unique:posts,slug' . $id,
        'tags' => 'tags'
    ];
}

Si on a ce paramètre, alors on ajoute l'id de l'article en cours de modification pour ne pas tomber sur une erreur de validation parasite parce que le slug existe forcément déjà dans la base.

V-C. L'enregistrement

Si la validation se passe bien le contrôleur fait appel à la méthode update du repository BlogRepository :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
/**
 * Update a post.
 *
 * @param  array  $inputs
 * @param  App\Models\Post $post
 * @return void
 */
public function update($inputs, $post)
{
    $post = $this->savePost($post, $inputs);
 
    // Tag gestion
    $tags_id = [];
    if(array_key_exists('tags',  $inputs) && $inputs['tags'] != '') {
 
        $tags = explode(',', $inputs['tags']);
 
        foreach ($tags as $tag) {
            $tag_ref = $this->tag->whereTag($tag)->first();
            if(is_null($tag_ref)) {
                $tag_ref = new $this->tag();    
                $tag_ref->tag = $tag;
                $tag_ref->save();
            } 
            array_push($tags_id, $tag_ref->id);
        }
    }
 
    $post->tags()->sync($tags_id);
}

On se retrouve avec un code très proche de celui pour la création.

VI. Supprimer un article

La suppression d'un article s'effectue à partir du bouton « Supprimer » de la liste des articles :

Image non disponible

C'est cette fois la méthode destroy du contrôleur BlogController qui est utilisée :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
/**
 * Remove the specified resource from storage.
 *
 * @param  int  $id
 * @return Response
 */
public function destroy($id)
{
    $post = $this->blog_gestion->getById($id);
 
    $this->authorize('change', $post);
 
    $this->blog_gestion->destroy($post);
 
    return redirect('blog')->with('ok', trans('back/blog.destroyed'));        
}

On a la même autorisation que pour la modification.

Le contrôleur appelle la méthode destroy du repository BlogRepository :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
/**
 * Destroy a post.
 *
 * @param  App\Models\Post $post
 * @return void
 */
public function destroy($post)
{
    $post->tags()->detach();
     
    $post->delete();
}

On commence par détacher les tags et ensuite on supprime l'article.

Ensuite le contrôleur redirige sur la même page avec un message en session :

Image non disponible

VII. Les commentaires

L'administrateur dispose également sur son tableau de bord d'un bloc pour les commentaires :

Image non disponible

Le code pour afficher le bloc est identique à celui que l'on a vu pour les articles.

Il y a également dans le menu un lien vers les commentaires :

Image non disponible

Voici le panneau de gestion des commentaires :

Image non disponible

L'administrateur peut ici :

  • supprimer un commentaire ;
  • valider l'utilisateur pour les commentaires ;
  • marquer le commentaire comme « lu ».

La vue utilisée est celle-ci :

Image non disponible

L'affichage des commentaires est assuré par une boucle :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
@foreach ($comments as $comment)
    <div class="panel {!! $comment->seen? 'panel-default' : 'panel-warning' !!}">
        <div class="panel-heading {!! $comment->user->valid? '' : 'border-red' !!}">
            <table class="table">
                <thead>
                    <tr>
                        <th class="col-lg-3">{{ trans('back/comments.author') }}</th>
                        <th class="col-lg-3">{{ trans('back/comments.date') }}</th>
                        <th class="col-lg-3">{{ trans('back/comments.post') }}</th>
                        <th class="col-lg-1">{{ trans('back/comments.valid') }}</th>
                        <th class="col-lg-1">{{ trans('back/comments.seen') }}</th>
                        <th class="col-lg-1"></th>
                    </tr>
                </thead>
                <tbody>
                <tr>
                    <td class="text-primary"><strong>{{ $comment->user->username }}</strong></td>
                    <td>{{ $comment->created_at }}</td>
                    <td>{{ $comment->post->title }}</td>
                    <td>{!! Form::checkbox('valide', $comment->user->id, $comment->user->valid) !!}</td>
                    <td>{!! Form::checkbox('seen', $comment->id, $comment->seen) !!}</td>
                    <td>
                            {!! Form::open(['method' => 'DELETE', 'route' => ['comment.destroy', $comment->id]]) !!}
                                {!! Form::destroy(trans('back/comments.destroy'), trans('back/comments.destroy-warning'), 'btn-xs') !!}
                            {!! Form::close() !!}
                    </td>
                </tr>
            </tbody>
            </table>  
        </div>
        <div class="panel-body">
            {!! $comment->content !!}
        </div> 
    </div>
@endforeach

VII-A. Supprimer un commentaire

La suppression d'un commentaire est assurée par la méthode destroy du contrôleur CommentController :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
/**
 * Remove the specified resource from storage.
 *
 * @param  Illuminate\Http\Request $request
 * @param  int  $id
 * @return Response
 */
public function destroy(
    Request $request, 
    $id)
{
    $this->comment_gestion->destroy($id);
 
    if($request->ajax())
    {
        return response()->json(['id' => $id]);
    }
 
    return redirect('comment');
}

Nous avons déjà vu cette méthode lorsque nous avons étudié le front-end. La requête était alors effectuée en Ajax. Dans la partie administration, on la fait de façon classique.

C'est la méthode destroy du repository de base Baserepository qui assure la suppression dans la base :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
/**
 * Destroy a model.
 *
 * @param  int $id
 * @return void
 */
public function destroy($id)
{
    $this->getById($id)->delete();
}

VII-B. Valider l'utilisateur

Par sécurité un utilisateur nouvellement inscrit ne voit pas apparaître ses commentaires, il faut qu'un administrateur le valide. Évidemment une fois que cette action est effectuée tous les autres commentaires apparaissent immédiatement.

Tant qu'un utilisateur n'est pas validé, tous ses commentaires apparaissent bordés de rouge :

Image non disponible

Le changement s'effectue par action sur la case à cocher « valide ». La requête est envoyée en Ajax :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
// Valide gestion
$(":checkbox[name='valide']").change(function() { 
    var cases = $(":checkbox[name='valide'][value='" + this.value + "']");
    cases.prop('checked', this.checked);
    cases.parents('.panel-heading').toggleClass('border-red');
    cases.hide().parent().append('<i class="fa fa-refresh fa-spin"></i>');
    var token = $('input[name="_token"]').val();
    $.ajax({
        url: '{!! url('uservalid') !!}' + '/' + this.value,
        type: 'PUT',
        data: "valid=" + this.checked + "&_token=" + token
    })
    .done(function() {
        $('.fa-spin').remove();
        $('input[type="checkbox"]:hidden').show();
    })
    .fail(function() {
        $('.fa-spin').remove();
        var cases = $('input[type="checkbox"]:hidden');
        cases.parents('.panel-heading').toggleClass('border-red'); 
        cases.show().prop('checked', cases.is(':checked') ? null:'checked');
        alert('{{ trans('back/comments.fail') }}');
    });
});

Une petite animation signifie à l'administrateur que l'action est en cours :

Image non disponible

La requête arrive à la méthode valid du contrôleur CommentController :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
/**
 * Validate an user
 *
 * @param  Illuminate\Http\Request $request
 * @param  App\Repositories\UserRepository $user_gestion
 * @param  int  $id
 * @return Response
 */
public function valid(
    Request $request, 
    UserRepository $user_gestion, 
    $id)
{
    $user_gestion->valide($request->input('valid'), $id);
     
    return response()->json();       
}

C'est finalement la méthode valide du repository UserRepository qui assure la modification dans la base :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
/**
 * Valid user.
 *
 * @param  bool  $valid
 * @param  int   $id
 * @return void
 */
public function valide($valid, $id)
{
    $user = $this->getById($id);
    $user->valid = $valid == 'true';
    $user->save();
}

VII-C. Marquer le commentaire

Un commentaire non lu apparaît avec un fond jaune :

Image non disponible

Le changement s'effectue par action sur la case à cocher « Vu ». La requête est envoyée en Ajax selon un principe identique à celui de la validation de l'utilisateur.

La requête arrive à la méthode updateSeen du contrôleur CommentController :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
/**
 * Update "seen" in the specified resource in storage.
 *
 * @param  Illuminate\Http\Request $request
 * @param  int  $id
 * @return Response
 */
public function updateSeen(
    Request $request, 
    $id)
{
    $this->comment_gestion->update($request->input('seen'), $id);
 
    return response()->json();       
}

C'est finalement la méthode update du repository CommentRepository qui assure la modification dans la base :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
/**
 * Update a comment.
 *
 * @param  bool  $vu
 * @param  int   $id
 * @return void
 */
public function update($seen, $id)
{
    $comment = $this->getById($id);
    $comment->seen = $seen == 'true';
    $comment->save();
}

Nous avons ainsi fait le tour de la gestion des articles côté back-end.

VIII. Remerciements

Nous remercions Maurice Chavelli qui nous autorise à publier ce tutoriel.

Nous tenons également à remercier Winjerome pour la mise au gabarit et Claude Leloup pour la relecture orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.