I. Les sommaires▲
Les articles sont accessibles à tous les visiteurs en cliquant sur BLOG dans le menu avec cet aspect :
On a alors les sommaires des articles qui apparaissent avec une pagination qui limite la génération à deux articles à la fois.
C'est la méthode indexFront du contrôleur BlogController qui est chargée de cet affichage :
Le repository est injecté dans le constructeur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
/**
* Create a new BlogController instance.
*
*
@param
App\Repositories\BlogRepository
$blog_gestion
...
*
@return
void
*/
public function __construct(
BlogRepository $blog_gestion
,
...
)
{
...
$this
->
blog_gestion =
$blog_gestion
;
$this
->
nbrPages =
2
;
...
}
C'est la méthode indexFront du repository BlogRepository qui est appelée :
Le nombre de pages à afficher est passé en paramètre.
On fait appel dans le repository à une fonction privée :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
/**
* Create a query for Post.
*
*
@return
Illuminate\Database\Eloquent\Builder
*/
private function queryActiveWithUserOrderByDate()
{
return $this
->
model
->
select('
id
'
,
'
created_at
'
,
'
updated_at
'
,
'
title
'
,
'
slug
'
,
'
user_id
'
,
'
summary
'
)
->
whereActive(true)
->
with('
user
'
)
->
latest();
}
On récupère ici les articles actifs classés par dates et leurs auteurs. Le contrôleur envoie ensuite tout ça, accompagné des liens de pagination à la vue :
Les articles sont générés dans une boucle :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
@foreach
($posts
as
$post
)
<
div class
=
"
box
"
>
<
div class
=
"
col-lg-12 text-center
"
>
<
h2>
{{
$post
->
title }}
<
br>
<
small>
{{
$post
->
user->
username }}
{{
trans('front/blog.on'
) }}
{!!
$post
->
created_at .
($post
->
created_at !=
$post
->
updated_at ?
trans('front/blog.updated'
) .
$post
->
updated_at :
''
) !!}
<
/small
>
<
/h2
>
<
/div
>
<
div class
=
"
col-lg-12
"
>
<
p>
{!!
$post
->
summary !!}
<
/p
>
<
/div
>
<
div class
=
"
col-lg-12 text-center
"
>
{!!
link_to('blog/'
.
$post
->
slug,
trans('front/blog.button'
),
[
'class'
=>
'btn btn-default btn-lg'
]
) !!}
<
hr>
<
/div
>
<
/div
>
@endforeach
II. Affichage d'un article▲
Lorsqu'on clique sur le bouton En lire plus on affiche l'article :
C'est la méthode show du contrôleur BlogController qui est chargée de cet affichage :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
/**
* Display the specified resource.
*
*
@param
Illuminate\Contracts\Auth\Guard
$auth
*
@param
string
$slug
*
@return
Response
*/
public function show(
Guard $auth
,
$slug
)
{
$user
=
$auth
->
user();
return view('
front.blog.show
'
,
array_merge($this
->
blog_gestion->
show($slug
),
compact('
user
'
)));
}
On a besoin de connaître l'utilisateur actuel pour une bonne gestion des commentaires.
C'est la méthode show du repository BlogRepository qui est appelée avec transmission du slug de l'article :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
/**
* Get post collection.
*
*
@param
string
$slug
*
@return
array
*/
public function show($slug
)
{
$post
=
$this
->
model->
with('
user
'
,
'
tags
'
)->
whereSlug($slug
)->
firstOrFail();
$comments
=
$this
->
comment
->
wherePost_id($post
->
id)
->
with('
user
'
)
->
whereHas('
user
'
,
function($q
) {
$q
->
whereValid(true);
}
)
->
get();
return compact('
post
'
,
'
comments
'
);
}
Ici on récupère l'article, accompagné de son auteur et des tags. On charge aussi les commentaires correspondants et on envoie tout ça au contrôleur qui lui les envoie dans la vue :
Voici le code de l'affichage hors commentaires :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
<
div class
=
"
row
"
>
<
div class
=
"
box
"
>
<
div class
=
"
col-lg-12
"
>
<
hr>
<
h2 class
=
"
text-center
"
>
{{
$post
->
title }}
<
br>
<
small>
{{
$post
->
user->
username }}
{{
trans('front/blog.on'
) }}
{!!
$post
->
created_at .
($post
->
created_at !=
$post
->
updated_at ?
trans('front/blog.updated'
) .
$post
->
updated_at :
''
) !!}
<
/small
>
<
/h2
>
<
hr>
{!!
$post
->
summary !!}
<
br>
{!!
$post
->
content !!}
<
hr>
@if
($post
->
tags->
count())
<
div class
=
"
text-center
"
>
@if
($post
->
tags->
count() >
0
)
<
small>
{{
trans('front/blog.tags'
) }}
<
/small
>
@foreach
($post
->
tags as
$tag
)
{!!
link_to('blog/tag?tag='
.
$tag
->
id,
$tag
->
tag,
[
'class'
=>
'btn btn-default btn-xs'
]
) !!}
@endforeach
@endif
<
/div
>
@endif
<
/div
>
<
/div
>
<
/div
>
III. Les tags▲
Lorsqu'on clique sur un bouton de tag, on doit afficher tous les articles qui correspondent à ce tag :
C'est la méthode tag du contrôleur BlogController qui est chargée de cet affichage :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
/**
* Get tagged posts
*
*
@param
Illuminate\Http\Request
$request
*
@return
Response
*/
public function tag(Request $request
)
{
$tag
=
$request
->
input('
tag
'
);
$posts
=
$this
->
blog_gestion->
indexTag($this
->
nbrPages,
$tag
);
$links
=
$posts
->
setPath(''
)->
appends(compact('
tag
'
))->
render();
$info
=
trans('
front/blog.info-tag
'
) .
'
<strong>
'
.
$this
->
blog_gestion->
getTagById($tag
) .
'
</strong>
'
;
return view('
front.blog.index
'
,
compact('
posts
'
,
'
links
'
,
'
info
'
));
}
L'index du tag est récupéré dans la requête qu'on injecte dans la méthode. Pour récupérer les articles, on utilise la méthode indexTag du repository BlogRepository :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
/**
* Get post collection.
*
*
@param
int
$n
*
@param
int
$id
*
@return
Illuminate\Support\Collection
*/
public function indexTag($n
,
$id
)
{
$query
=
$this
->
queryActiveWithUserOrderByDate();
return $query
->
whereHas('
tags
'
,
function($q
) use($id
) {
$q
->
where('
tags.id
'
,
$id
);
}
)
->
paginate($n
);
}
Cette méthode ressemble beaucoup à celle qu'on a vue pour l'affichage des articles (indexFront) puisqu'on fait appel à la même fonction privée queryActiveWithUserOrderByDate. La différence réside dans le fait qu'on va limiter les articles à ceux qui possèdent le tag.
On a également besoin dans la vue du nom du tag pour la barre d'information :
C'est la méthode getTagById du repository BlogRepository qui nous le donne :
Le contrôleur envoie toutes les informations à la même vue que celle que nous avons vue ci-dessus pour l'affichage classique des articles, mais en transmettant en plus l'information pour la barre :
C'est au niveau du template (resources/views/front/template.blade.php) que s'effectue cet affichage :
IV. La recherche▲
Il est prévu un champ de saisie pour la recherche dans les articles :
C'est la méthode search du contrôleur BlogController qui est chargée de cet affichage :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
/**
* Find search in blog
*
*
@param
App\Http\Requests\SearchRequest
$request
*
@return
Response
*/
public function search(SearchRequest $request
)
{
$search
=
$request
->
input('
search
'
);
$posts
=
$this
->
blog_gestion->
search($this
->
nbrPages,
$search
);
$links
=
$posts
->
setPath(''
)->
appends(compact('
search
'
))->
render();
$info
=
trans('
front/blog.info-search
'
) .
'
<strong>
'
.
$search
.
'
</strong>
'
;
return view('
front.blog.index
'
,
compact('
posts
'
,
'
links
'
,
'
info
'
));
}
Elle ressemble évidemment beaucoup à la méthode tag vue ci-dessus.
La validation est assurée par la requête de formulaire SearchRequest avec des règles simples :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
<?php
namespace
App\Http\Requests;
class
SearchRequest extends
Request {
/**
* Get the validation rules that apply to the request.
*
*
@return
array
*/
public
function
rules()
{
return
[
'search'
=>
'required|max:100'
,
];
}
}
C'est la méthode search du repository BlogRepository qui est appelée par le contrôleur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
/**
* Get search collection.
*
*
@param
int
$n
*
@param
string
$search
*
@return
Illuminate\Support\Collection
*/
public function search($n
,
$search
)
{
$query
=
$this
->
queryActiveWithUserOrderByDate();
return $query
->
where(function($q
) use ($search
) {
$q
->
where('
summary
'
,
'
like
'
,
"
%
$search
%
"
)
->
orWhere('
content
'
,
'
like
'
,
"
%
$search
%
"
);
}
)->
paginate($n
);
}
On utilise à nouveau la fonction privée queryActiveWithUserOrderByDate. Ensuite on sélectionne les articles avec la recherche passée en paramètre. On applique enfin la pagination.
Le retour par le contrôleur est exactement le même que pour les tags :
En effet c'est la même vue et on envoie un message pour la barre :
V. Les commentaires▲
V-A. Affichage▲
Les articles sont affichés avec une zone de commentaire dans leur partie inférieure :
On a vu ci-dessus que c'est la méthode show du repository BlogRepository qui récupère les commentaires dans la base pour l'article.
Au niveau de la vue (resources/views/front/blog/show.blade.php) ils sont générés dans une boucle :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
@if
($comments
->
count())
@foreach
($comments
as
$comment
)
<
div class
=
"
commentitem
"
>
<
h3>
<
small>
{{
$comment
->
user->
username .
' '
.
trans('front/blog.on'
) .
' '
.
$comment
->
created_at }}
<
/small
>
@if
($user
&&
$user
->
username ==
$comment
->
user->
username)
<
a id
=
"
deletecomment
{!!
$comment
->
id !!}
"
href
=
"
#
"
class
=
"
deletecomment
"
><span class
=
"
fa fa-fw fa-trash pull-right
"
data-toggle
=
"
tooltip
"
data-placement
=
"
left
"
title
=
"
{{
trans('front/blog.delete'
) }}
"
></span
></a
>
<
a id
=
"
comment
{!!
$comment
->
id !!}
"
href
=
"
#
"
class
=
"
editcomment
"
><span class
=
"
fa fa-fw fa-pencil pull-right
"
data-toggle
=
"
tooltip
"
data-placement
=
"
left
"
title
=
"
{{
trans('front/blog.edit'
) }}
"
></span
></a
>
@endif
<
/h3
>
<
div id
=
"
contenu
{!!
$comment
->
id !!}
"
>
{!!
$comment
->
content !!}
<
/div
>
<
hr>
<
/div
>
@endforeach
@endif
D'autre part si l'utilisateur est authentifié, on génère aussi un formulaire :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
<
div class
=
"
row
"
id
=
"
formcreate
"
>
@if
(session()->
has('warning'
))
@
include('partials/error'
,
[
'type'
=>
'warning'
,
'message'
=>
session('warning'
)]
)
@endif
@if
(session('statut'
) !=
'visitor'
)
{!!
Form::
open([
'url'
=>
'comment'
]
) !!}
{!!
Form::
hidden('post_id'
,
$post
->
id) !!}
{!!
Form::
control('textarea'
,
12
,
'comments'
,
$errors
,
trans('front/blog.comment'
)) !!}
{!!
Form::
submit(trans('front/form.send'
),
[
'col-lg-12'
]
) !!}
{!!
Form::
close() !!}
@else
<
div class
=
"
text-center
"
><i class
=
"
text-center
"
>
{{
trans('front/blog.info-comment'
) }}
<
/i
></div
>
@endif
<
/div
>
S'il n'est pas authentifié, il a à la place un petit message :
V-B. Soumission▲
C'est la méthode store du contrôleur CommentController qui est chargée de gérer la soumission d'un commentaire :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
/**
* Store a newly created resource in storage.
*
*
@param
App\requests\CommentRequest
$request
*
@return
Response
*/
public function store(
CommentRequest $request
)
{
$this
->
comment_gestion->
store($request
->
all(),
$request
->
user()->
id);
if($request
->
user()->
valid)
{
return redirect()->
back();
}
return redirect()->
back()->
with('
warning
'
,
trans('
front/blog.warning
'
));
}
La validation est assurée par la requête de formulaire CommentRequest :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
<?php
namespace
App\Http\Requests;
class
CommentRequest extends
Request {
/**
* Get the validation rules that apply to the request.
*
*
@return
array
*/
public
function
rules()
{
$id
=
$this
->
comment;
return
[
'comments'
.
$id
=>
'required|max:65000'
,
];
}
}
Le contrôleur appelle la méthode store du repository CommentRepository :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
/**
* Store a comment.
*
*
@param
array
$inputs
*
@param
int
$user_id
*
@return
void
*/
public function store($inputs
,
$user_id
)
{
$comment
=
new $this
->
model;
$comment
->
content =
$inputs
[
'
comments
'
];
$comment
->
post_id =
$inputs
[
'
post_id
'
];
$comment
->
user_id =
$user_id
;
$comment
->
save();
}
Le contrôleur renvoie alors la même page, avec le commentaire créé et un message si l'utilisateur n'a pas encore été validé pour les commentaires :
C'est cette partie de la vue (resources/views/front/blog/show.blade.php) qui gère ce commentaire :
2.
3.
@if
(session()->
has('warning'
))
@
include('partials/error'
,
[
'type'
=>
'warning'
,
'message'
=>
session('warning'
)]
)
@endif
Et évidemment en cas de problème de validation c'est signalé à l'utilisateur :
V-C. Suppression▲
Un utilisateur peut supprimer son commentaire. Il dispose pour cela d'une icône représentant une poubelle :
Évidemment ces icônes n'apparaissent que si l'utilisateur connecté est l'auteur du commentaire :
2.
3.
4.
@if
($user
&&
$user
->
username ==
$comment
->
user->
username)
<
a id
=
"
deletecomment
{!!
$comment
->
id !!}
"
href
=
"
#
"
class
=
"
deletecomment
"
><span class
=
"
fa fa-fw fa-trash pull-right
"
data-toggle
=
"
tooltip
"
data-placement
=
"
left
"
title
=
"
{{
trans('front/blog.delete'
) }}
"
></span
></a
>
<
a id
=
"
comment
{!!
$comment
->
id !!}
"
href
=
"
#
"
class
=
"
editcomment
"
><span class
=
"
fa fa-fw fa-pencil pull-right
"
data-toggle
=
"
tooltip
"
data-placement
=
"
left
"
title
=
"
{{
trans('front/blog.edit'
) }}
"
></span
></a
>
@endif
La requête est envoyée en Ajax avec ce code :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
// Delete comment
$(
'a.deletecomment'
).click
(
function(
e) {
e.preventDefault
(
);
if (!
confirm
(
'
{{
trans('front/blog.confirm'
) }}
'
)) return;
var i =
$(
this).attr
(
'id'
).substring
(
13
);
var token =
$(
'input[name="_token"]'
).val
(
);
$(
this).replaceWith
(
'<i class="fa fa-refresh fa-spin pull-right"></i>'
);
$.ajax
({
method
:
'delete'
,
url
:
'
{!!
url('comment'
) !!}
'
+
'/'
+
i,
data
:
'_token='
+
token
}
)
.done
(
function(
data) {
$(
'#comment'
+
data.
id).parents
(
'.commentitem'
).remove
(
);
}
)
.fail
(
function(
) {
alert
(
'
{{
trans('front/blog.fail-delete'
) }}
'
);
}
);
}
);
C'est la méthode destroy du contrôleur CommentController qui est chargée de gérer la suppression d'un commentaire :
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
'
);
}
On verra que cette méthode est aussi utilisée par le back-end sans Ajax. D'où la présence d'un test de la requête.
Cette méthode est protégée par le middleware auth au niveau du constructeur :
$this
->
middleware('
auth
'
,
[
'
only
'
=>
[
'
store
'
,
'
update
'
,
'
destroy
'
]]
);
Par contre, je n'ai jugé utile de vérifier qu'il s'agit réellement de l'auteur du commentaire.
C'est la méthode destroy du repository de base BaseRepository qui effectue la suppression dans la base :
V-D. Modification▲
Voyons maintenant la partie la plus délicate qui concerne la modification d'un commentaire. Délicate parce qu'il faut :
- générer dynamiquement un formulaire ;
- donner la possibilité d'annuler l'action ;
- gérer les erreurs de saisie avec forcément une requête en Ajax ;
- ne pas mélanger les formulaires de la page.
Pour modifier un article, l'utilisateur a un bouton à disposition :
Il obtient ainsi un formulaire :
Il conserve la possibilité de supprimer son commentaire avec la petite poubelle. Il peut aussi annuler l'action avec le bouton « Annuler ». Il peut enfin soumettre le formulaire avec le bouton « Valider ».
Tout cela est évidemment géré côté client en JavaScript :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
// Set comment edition
$(
'a.editcomment'
).click
(
function(
e) {
e.preventDefault
(
);
$(
this).hide
(
);
var i =
$(
this).attr
(
'id'
).substring
(
7
);
var existing =
$(
'#contenu'
+
i).html
(
);
var url =
$(
'#formcreate'
).find
(
'form'
).attr
(
'action'
);
jQuery.data
(
document
.
body,
'comment'
+
i,
existing);
var html =
"<div class='row'><form id='form"
+
i +
"' method='POST' action='"
+
url +
'/'
+
i +
"' accept-charset='UTF-8' class='formajax'><input name='_token' type='hidden' value='"
+
$(
'input[name="_token"]'
).val
(
) +
"'><div class='form-group col-lg-12 '><label for='comments' class='control-label'>
{{
trans('front/blog.change'
) }}
</label><textarea id='cont"
+
i +
"' class='form-control' name='comments"
+
i +
"' cols='50' rows='10' id='comments"
+
i +
"'>"
+
existing +
"</textarea><small class='help-block'></small></div><div class='form-group col-lg-12'>"
+
buttons
(
i) +
"</div>"
;
$(
'#contenu'
+
i).html
(
html);
CKEDITOR.replace
(
'comments'
+
i,
{
language
:
'
{{
config('app.locale'
) }}
'
,
height
:
200
,
toolbarGroups
:
[
{
name
:
'basicstyles'
,
groups
:
[
'basicstyles'
]
},
{
name
:
'links'
},
{
name
:
'insert'
}
],
removeButtons
:
'Table,SpecialChar,HorizontalRule,Anchor'
}
);
}
);
Je ne vais pas détailler le fonctionnement puisqu'il n'a rien à voir avec Laravel à ce niveau et pourrait être traité de manière différente, par exemple avec AngularJS. Par contre, on va s'intéresser à la partie soumission en Ajax parce qu'ici Laravel intervient.
C'est la méthode update du contrôleur CommentController qui est chargée de gérer la modification d'un commentaire :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
/**
* Update the specified resource in storage.
*
*
@param
App\requests\CommentRequest
$request
*
@param
int
$id
*
@return
Response
*/
public function update(
CommentRequest $request
,
$id
)
{
$id
=
$request
->
segment(2
);
$content
=
$request
->
input('
comments
'
.
$id
);
$this
->
comment_gestion->
updateContent($content
,
$id
);
return response()->
json([
'
id
'
=>
$id
,
'
content
'
=>
$content
]
);
}
On voit que la validation est réalisée par la requête de formulaire CommentRequest que nous avons déjà vue ci-dessus pour la création.
La soumission côté client est gérée par ce code JavaScript :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
// Validation
$(
document
).on
(
'submit'
,
'.formajax'
,
function(
e) {
e.preventDefault
(
);
var i =
$(
this).attr
(
'id'
).substring
(
4
);
$(
'#val'
+
i).parent
(
).html
(
'<i class="fa fa-refresh fa-spin fa-2x"></i>'
).addClass
(
'text-center'
);
$.ajax
({
method
:
'put'
,
url
:
$(
this).attr
(
'action'
),
data
:
$(
this).serialize
(
)
}
)
.done
(
function(
data) {
$(
'#comment'
+
data.
id).show
(
);
$(
'#contenu'
+
data.
id).html
(
data.
content);
}
)
.fail
(
function(
data) {
var errors =
data.
responseJSON;
$.each
(
errors,
function(
index,
value) {
$(
'textarea[name="'
+
index +
'"]'
+
' ~ small'
).text
(
value);
$(
'textarea[name="'
+
index +
'"]'
).parent
(
).addClass
(
'has-error'
);
$(
'.fa-spin'
).parent
(
).html
(
buttons
(
index.slice
(-
1
))).removeClass
(
'text-center'
);
}
);
}
);
}
);
Si la validation est correcte, le contrôleur renvoie une réponse JSON en transmettant l'identifiant du commentaire et son contenu. On régénère le commentaire avec le nouveau contenu.
C'est la méthode updateContent du repository CommentRepository qui fait la mise à jour dans la base :
Si la validation n'est pas correcte, il faut le signaler à l'utilisateur. Donc on récupère les erreurs (ici en fait une seule peut arriver) et on adapte le DOM en conséquence.
VI. 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.