VI. La relation 1:n▲
Pour le moment nous n'avons manipulé qu'une table avec Eloquent. Dans le présent chapitre, nous allons utiliser deux tables et les mettre en relation.
La relation la plus répandue et la plus simple entre deux tables est celle qui fait correspondre un enregistrement d'une table à plusieurs enregistrements de l'autre table, on parle de relation de un à plusieurs ou encore de relation de type 1:n. Nous verrons également dans ce chapitre comment créer un middleware.
Comme exemple pour ce chapitre, je vais prendre le cas d'un petit blog personnel avec :
- un affichage des articles ;
- des visiteurs qui pourront consulter les articles ;
- des utilisateurs enregistrés qui pourront aussi rédiger des articles (donc possibilité de se connecter et se déconnecter) ;
- des administrateurs qui pourront aussi supprimer des articles.
Pour ne pas trop alourdir le code, je ne vais pas prévoir la modification des articles.
Partez d'une installation fraîche de Laravel et utilisez la commande d'Artisan que nous avons vue précédemment pour créer les éléments de l'authentification :
php artisan make:auth
Nous allons utiliser cette infrastructure de base pour créer notre exemple.
VI-A. Les migrations▲
VI-A-1. Table users▲
Pour distinguer les rôles nous allons ajouter dans la table users une colonne admin. Dans la migration par défaut ajoutez cette ligne :
On va ainsi créer un champ booléen admin avec comme valeur par défaut false. Ne changez rien au reste du code.
VI-A-2. Table posts▲
On va créer une migration pour la table des articles posts :
php artisan make:migration create_posts_table
Complétez le code ainsi :
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.
<?php
use
Illuminate\Support\Facades\Schema;
use
Illuminate\Database\Schema\Blueprint;
use
Illuminate\Database\Migrations\Migration;
class
CreatePostsTable extends
Migration
{
public
function
up()
{
Schema::
create('posts'
,
function
(Blueprint $table
) {
$table
->
increments('id'
);
$table
->
timestamps();
$table
->
string('titre'
);
$table
->
text('contenu'
);
$table
->
integer('user_id'
)->
unsigned();
$table
->
foreign('user_id'
)
->
references('id'
)
->
on('users'
)
->
onDelete('restrict'
)
->
onUpdate('restrict'
);
}
);
}
public
function
down()
{
Schema::
table('posts'
,
function
(Blueprint $table
) {
$table
->
dropForeign('posts_user_id_foreign'
);
}
);
Schema::
drop('posts'
);
}
}
Normalement vous devez avoir ces trois migrations :
Lancez les migrations :
Vous devez vous retrouver avec les trois tables dans votre base ainsi que la table migrations :
VI-B. La relation▲
On a la situation suivante :
- un utilisateur peut écrire plusieurs articles ;
- un article est écrit par un seul utilisateur.
Il faut trouver un moyen pour référencer cette relation dans les tables. Le principe est simple : on prévoit dans la table posts une ligne destinée à recevoir l'identifiant de l'utilisateur rédacteur de l'article. On appelle cette ligne une clé étrangère parce qu'on enregistre ici la clé d'une autre table. Voici une représentation visuelle de cette relation :
Vous voyez la relation dessinée entre la clé id dans la table users et la clé étrangère user_id dans la table posts. La migration que l'on a créée ci-dessus est destinée aussi à informer la base de cette relation. Regardez ce code :
2.
3.
4.
5.
$table
->
foreign('
user_id
'
)
->
references('
id
'
)
->
on('
users
'
)
->
onDelete('
restrict
'
)
->
onUpdate('
restrict
'
);
Dans la table on déclare une clé étrangère (foreign) nommée user_id qui référence (references) la ligne id dans la table (on) users. En cas de suppression (onDelete) ou de modification (onUpdate) on a une restriction (restrict).
Que signifient ces deux dernières conditions ?
Imaginez que vous avez un utilisateur avec l'id 5 qui a deux articles, donc dans la table posts on a deux enregistrements avec user_id qui a la valeur 5. Si on supprime l'utilisateur que va-t-il se passer ? On risque de se retrouver avec nos deux enregistrements dans la table posts avec une clé étrangère qui ne correspond à aucun enregistrement dans la table users. En mettant restrict, on empêche la suppression d'un utilisateur qui a des articles. On doit donc commencer par supprimer ses articles avant de le supprimer lui-même. On dit que la base assure l'intégrité référentielle. Elle n'acceptera pas non plus qu'on utilise pour user_id une valeur qui n'existe pas dans la table users.
Une autre possibilité est cascade à la place de restrict. Dans ce cas si vous supprimez un utilisateur ça supprimera en cascade les articles de cet utilisateur.
C'est une option qui est rarement utilisée parce qu'elle peut s'avérer dangereuse, surtout dans une base comportant de multiples tables en relation. Mais c'est aussi une stratégie très efficace parce que c'est le moteur de la base de données qui se charge de gérer les enregistrements en relation, vous n'avez ainsi pas à vous en soucier au niveau du code.
On pourrait aussi ne pas signaler à la base qu'il existe une relation et la gérer seulement dans notre code. Mais c'est encore plus dangereux parce que la moindre erreur de gestion des enregistrements dans votre code risque d'avoir des conséquences importantes dans votre base avec de multiples incohérences.
VI-C. Les modèles▲
Nous avons déjà un modèle User (app/User.php). Il va juste falloir ajouter une méthode pour pouvoir facilement aller trouver les articles d'un utilisateur. Ajoutez ce code dans le modèle User :
On déclare ici qu'un utilisateur a plusieurs (hasMany) articles (posts). On aura ainsi une méthode pratique pour récupérer les articles d'un utilisateur.
Soyez vigilant avec les espaces de noms !
Il nous faut aussi le modèle Post :
php artisan make:model Post
Complétez ainsi le code :
Ici on a la méthode user (au singulier) qui permet de trouver l'utilisateur auquel appartient (belongsTo) l'article. C'est donc la réciproque de la méthode précédente.
Voici une schématisation de cette relation avec les deux méthodes :
Si vous ne spécifiez pas de manière explicite le nom de la table dans un modèle, Laravel le déduit à partir du nom du modèle en le mettant au pluriel (à la mode anglaise) et en mettant la première lettre en minuscule. Donc avec le modèle Post il en conclut que la table s'appelle posts. Si ce n'était pas satisfaisant il faudrait créer une propriété $table.
Les deux méthodes mises en place permettent de récupérer facilement un enregistrement lié. Par exemple pour avoir tous les articles de l'utilisateur qui a l'id 1 :
De la même manière on peut trouver l'utilisateur qui a écrit l'article d'id 1 :
Vous voyez que le codage devient limpide avec ces méthodes.
VI-D. Le contrôleur et les routes▲
VI-D-1. Le contrôleur▲
Maintenant que tout est en place au niveau des données, voyons un peu la gestion de tout ça. On va créer un contrôleur de ressource pour les articles qu'on va appeler PostController :
php artisan make:controller PostController -resource
Ce contrôleur devra gérer plusieurs choses :
- la réception de la requête pour afficher les articles du blog et la réponse adaptée ;
- la réception de la requête pour le formulaire pour créer un nouvel article et son envoi ;
- la réception de la soumission du formulaire de création d'un nouvel article (réservé à un utilisateur connecté) et son enregistrement ;
- la réception de la demande de suppression d'un article (réservé à un administrateur) et sa suppression.
Pour simplifier, je ne vais pas prévoir la possibilité de modifier un article.
Voici le code modifié du contrôleur :
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.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
<?php
namespace
App\Http\Controllers;
use
App\Repositories\PostRepository;
use
App\Http\Requests\PostRequest;
use
App\Post;
class
PostController extends
Controller
{
protected
$postRepository
;
protected
$nbrPerPage
=
4
;
public
function
__construct
(PostRepository $postRepository
)
{
$this
->
middleware('auth'
)->
except('index'
);
$this
->
middleware('admin'
)->
only('destroy'
);
$this
->
postRepository =
$postRepository
;
}
public
function
index()
{
$posts
=
$this
->
postRepository->
getPaginate($this
->
nbrPerPage);
return
view('posts.liste'
,
compact('posts'
));
}
public
function
create()
{
return
view('posts.create'
);
}
public
function
store(PostRequest $request
)
{
$inputs
=
array_merge($request
->
all(),
[
'user_id'
=>
$request
->
user()->
id]
);
$this
->
postRepository->
store($inputs
);
return
redirect()->
route('post.index'
);
}
public
function
destroy(Post $post
)
{
$this
->
postRepository->
destroy($post
);
return
back();
}
}
Comme à l'accoutumée j'injecte la requête de formulaire et le repository.
Notez l'utilisation des middlewares pour filtrer les utilisateurs, nous allons voir cela un peu plus loin.
VI-D-2. Les routes▲
On a vu dans le chapitre sur les ressources comment créer les routes de ce genre de contrôleur. Il va juste falloir indiquer qu'on ne veut pas utiliser les sept méthodes disponibles, mais juste certaines. On va aussi établir une redirection de la route de base « / » vers la ressource :
Pour mémoire Auth::routes(); a été généré par Artisan pour créer les routes de l'authentification.
Vous avez ainsi toutes ces routes :
VI-E. Le repository▲
Pour la gestion on va placer les fichiers dans le dossier app/Repositories comme nous l'avons déjà fait pour la gestion des utilisateurs :
Avec ce code :
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.
<?php
namespace
App\Repositories;
use
App\Post;
class
PostRepository
{
protected
$post
;
public
function
__construct
(Post $post
)
{
$this
->
post =
$post
;
}
public
function
getPaginate($n
)
{
return
$this
->
post->
with('user'
)
->
orderBy('posts.created_at'
,
'desc'
)
->
paginate($n
);
}
public
function
store($inputs
)
{
$this
->
post->
create($inputs
);
}
public
function
destroy(Post $post
)
{
$post
->
delete();
}
}
Nous allons voir plus loin l'utilité de toutes ces méthodes.
VI-F. Les middlewares▲
On a vu que dans le contrôleur on applique deux middlewares :
- auth : accès réservé aux utilisateurs authentifiés à part pour la méthode index pour afficher le blog,
- admin : accès réservé aux administrateurs pour la méthode destroy.
On a déjà rencontré le premier middleware, il est déjà prévu dans Laravel, par contre le second n'existe pas.
Il faut donc le créer. Encore une fois nous allons utiliser Artisan :
php artisan make:middleware Admin
On trouve le fichier bien rangé :
Modifiez ainsi le code :
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\Middleware;
use
Closure;
class
Admin
{
/**
* Handle an incoming request.
*
*
@param
\Illuminate\Http\Request
$request
*
@param
\Closure
$next
*
@return
mixed
*/
public
function
handle($request
,
Closure $next
)
{
if
($request
->
user()->
admin) {
return
$next
($request
);
}
return
redirect('post'
);
}
}
Si l'utilisateur n'est pas un administrateur, on redirige sur l'affichage du blog. Remarquez qu'on ne vérifie pas à ce niveau qu'on a un utilisateur authentifié parce que dans le constructeur du contrôleur le filtre auth est placé avant le filtre admin. Si c'était l'inverse, on tomberait évidemment sur une erreur en cas de tentative d'accès à l'URL pour la suppression d'un article.
On a créé le middleware, mais ça ne suffit pas, il faut maintenant un lien entre son nom et la classe qu'on vient de créer.
Regardez dans le fichier app/Http/Kernel.php ces lignes de code :
2.
3.
4.
5.
6.
7.
8.
protected $routeMiddleware
=
[
'
auth
'
=>
\Illuminate\Auth\Middleware\Authenticate::
class,
'
auth.basic
'
=>
\Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::
class,
'
bindings
'
=>
\Illuminate\Routing\Middleware\SubstituteBindings::
class,
'
can
'
=>
\Illuminate\Auth\Middleware\Authorize::
class,
'
guest
'
=>
\App\Http\Middleware\RedirectIfAuthenticated::
class,
'
throttle
'
=>
\Illuminate\Routing\Middleware\ThrottleRequests::
class,
];
Vous trouvez ici tous les middlewares déclarés, il suffit d'ajouter le nouveau :
VI-G. La validation▲
Voyons maintenant la validation. On va encore créer une requête de formulaire :
php artisan make:request PostRequest
Elle se place dans le dossier qui se crée pour l'occasion :
Et on complète ainsi le code :
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.
<?php
namespace
App\Http\Requests;
use
Illuminate\Foundation\Http\FormRequest;
class
PostRequest extends
FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
*
@return
bool
*/
public
function
authorize()
{
return
true
;
}
/**
* Get the validation rules that apply to the request.
*
*
@return
array
*/
public
function
rules()
{
return
[
'titre'
=>
'bail|required|max:255'
,
'contenu'
=>
'required'
];
}
}
VI-H. La population▲
VI-H-1. Les fabriques (model factories)▲
Nous allons voir maintenant comment remplir nos tables avec des enregistrements pour faire nos essais. Laravel nous permet de définir un tableau d'attributs pour les modèles avec les fabriques (model factories). Regardez le fichier database/factories/ModelFactory.php :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
<?php
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| Here you may define all of your model factories. Model factories give
| you a convenient way to create models for testing and seeding your
| database. Just tell the factory how a default model should look.
|
*/
$factory
->
define(App\User::
class
,
function
(Faker\Generator $faker
) {
static
$password
;
return
[
'name'
=>
$faker
->
name,
'email'
=>
$faker
->
safeEmail,
'password'
=>
$password
?:
$password
=
bcrypt('secret'
),
'remember_token'
=>
str_random(10
),
];
}
);
Faker est un générateur de données virtuelles aléatoires qui sait pratiquement tout faire.
Comme on a ajouté la colonne admin on va la prévoir dans la fabrique. D'autre part on va utiliser un mot de passe systématique pour nous simplifier la vie :
On va aussi prévoir une fabrique pour les articles dans le même fichier :
la documentation détaillée.
VI-H-2. La population▲
Regardez le fichier database/seeds/DatabaseSeeder :
Par défaut il comporte ce code :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<?php
use
Illuminate\Database\Seeder;
class
DatabaseSeeder extends
Seeder
{
/**
* Run the database seeds.
*
*
@return
void
*/
public
function
run()
{
// $this->call(UsersTableSeeder::class);
}
}
La méthode run est destinée à exécuter les fichiers pour la population. Vous avez déjà la ligne commentée de lancement pour la table users. Comme on n'a que deux tables à remplir, on va mettre tout le code dans ce fichier :
On appelle la fabrique (factory) pour le modèle des utilisateurs (App\User::class), on en veut 5, on les crée (create) pour chacun des utilisateurs créés (each $user) on utilise la relation (posts()) pour créer plusieurs (saveMany) articles (App\Post::class) en utilisant la fabrique (factory), on en veut entre 2 et 5 (rand(2, 5)).
Il ne vous reste plus qu'à lancer la population :
php artisan db:seed
Vous aurez dans la base cinq utilisateurs :
Et des articles (table posts) affectés aux utilisateurs :
On a des dates aléatoires pour la colonne created_at, ce qui va nous permettre de les trier.
VI-I. Fonctionnement▲
VI-I-1. La liste des articles▲
La liste des articles est obtenue avec l'URL (méthode get) : …/post
Elle arrive sur la méthode index du contrôleur :
Ici on envoie le nombre d'articles par page (placé dans la propriété $nbrPerPage) à la méthode getPaginate du repository :
On veut les articles avec (with) l'utilisateur (user), dans l'ordre des dates de création (created_at) descendant (desc) avec une pagination de n articles ($n).
Il existe la méthode latest (et oldest pour l'inverse) qui permet de simplifier la syntaxe :
return $this
->
post->
with('
user
'
)
->
latest()
->
paginate($n
);
VI-I-2. L'ajout d'un article▲
La demande du formulaire de création d'un article se fait avec l'URL (méthode get) : …/post/create
Le contrôleur renvoie directement la vue :
Le retour du formulaire se fait avec l'URL (méthode post) : …/post
Remarquez que cette méthode est protégée par le middleware auth :
$this->
middleware
(
'auth'
)->
except
(
'index'
);
On arrive sur la méthode store du contrôleur :
2.
3.
4.
5.
6.
7.
8.
public function store(PostRequest $request
)
{
$inputs
=
array_merge($request
->
all(),
[
'
user_id
'
=>
$request
->
user()->
id]
);
$this
->
postRepository->
store($inputs
);
return redirect()->
route('
post.index
'
);
}
On injecte la requête de formulaire pour la validation, je n'insiste pas parce qu'il n'y a rien de nouveau à ce niveau. On récupère les entrées du formulaire pour le titre et le contenu. Pour l'identifiant de l'utilisateur, on sait qu'il est forcément connecté alors on récupère cet identifiant avec la requête. Si la validation se passe bien on envoie à la méthode store du repository :
L'assignement de masse fonctionne parce qu'on a prévu la propriété $fillable dans le modèle Post :
protected $fillable
=
[
'
titre
'
,
'
contenu
'
,
'
user_id
'
];
VI-I-3. Suppression d'un article▲
Enfin on supprime un article avec l'URL (méthode delete) : …/post/id
Où id représente l'identifiant de l'article à supprimer. On tombe sur la méthode destroy du contrôleur :
Remarquez que cette méthode est protégée par le middleware admin :
$this
->
middleware('
admin
'
)->
only('
destroy
'
);
Avec la liaison implicite on a directement le modèle de l'article correspondant dans la variable $post. On l'envoie à la méthode destroy du repository :
Là on supprime l'article avec la méthode delete du modèle.
VI-J. Le template▲
Voyons à présent les vues. On dispose déjà d'un template (resources/views/layouts/app.blade.php) avec l'installation de l'authentification :
Alors on va l'utiliser pour avoir une cohérence visuelle de l'application. On va juste lui apporter des petites modifications (changement du titre, francisation, ajout d'un item dans la barre de navigation) , voici le code résultant :
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.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
<!
DOCTYPE html
>
<
html lang
=
"
fr
"
>
<
head>
<
meta charset
=
"
utf-8
"
>
<
meta http-equiv
=
"
X-UA-Compatible
"
content
=
"
IE=edge
"
>
<
meta name
=
"
viewport
"
content
=
"
width=device-width, initial-scale=1
"
>
<!-- CSRF Token -->
<
meta name
=
"
csrf-token
"
content
=
"
{{
csrf_token() }}
"
>
<
title>
Mon joli blog<
/title
>
<!-- Styles -->
<
link href
=
"
/css/app.css
"
rel
=
"
stylesheet
"
>
<!-- Scripts -->
<script>
window
.
Laravel =
<?php
echo json_encode([
'csrfToken'
=>
csrf_token(),
]
);
?>
</
script>
<
/head
>
<
body>
<
nav class
=
"
navbar navbar-default navbar-static-top
"
>
<
div class
=
"
container
"
>
<
div class
=
"
navbar-header
"
>
<!-- Collapsed Hamburger -->
<
button type
=
"
button
"
class
=
"
navbar-toggle collapsed
"
data-toggle
=
"
collapse
"
data-target
=
"
#app-navbar-collapse
"
>
<
span class
=
"
sr-only
"
>
Toggle Navigation<
/span
>
<
span class
=
"
icon-bar
"
></span
>
<
span class
=
"
icon-bar
"
></span
>
<
span class
=
"
icon-bar
"
></span
>
<
/button
>
<!-- Branding Image -->
<
a class
=
"
navbar-brand
"
href
=
"
{{
URL('/'
) }}
"
>
Mon joli Blog
<
/a
>
<
/div
>
<
div class
=
"
collapse navbar-collapse
"
id
=
"
app-navbar-collapse
"
>
<!-- Left Side Of Navbar -->
<
ul class
=
"
nav navbar-nav
"
>
<
/ul
>
<!-- Right Side Of Navbar -->
<
ul class
=
"
nav navbar-nav navbar-right
"
>
<!-- Authentication Links -->
@if
(Auth
::
guest
())
<
li><a href
=
"
{{
URL('/login'
) }}
"
>
Se connecter<
/a
></li
>
<
li><a href
=
"
{{
URL('/register'
) }}
"
>
S'enregistrer<
/a
></li
>
@else
<
li><a href
=
"
{{
URL('/post/create'
) }}
"
>
Créer un article<
/a
></li
>
<
li class
=
"
dropdown
"
>
<
a href
=
"
#
"
class
=
"
dropdown-toggle
"
data-toggle
=
"
dropdown
"
role
=
"
button
"
aria-expanded
=
"
false
"
>
{{
Auth::
user()->
name }}
<
span class
=
"
caret
"
></span
>
<
/a
>
<
ul class
=
"
dropdown-menu
"
role
=
"
menu
"
>
<
li>
<
a href
=
"
{{
url('/logout'
) }}
"
onclick
=
"
event.preventDefault();
document.getElementById('logout-form').submit();
"
>
Logout
<
/a
>
<
form id
=
"
logout-form
"
action
=
"
{{
url('/logout'
) }}
"
method
=
"
POST
"
style
=
"
display: none;
"
>
{{
csrf_field() }}
<
/form
>
<
/li
>
<
/ul
>
<
/li
>
@endif
<
/ul
>
<
/div
>
<
/div
>
<
/nav
>
@yield
('content'
)
<!-- Scripts -->
<script src
=
"
/js/app.js
"
></
script>
<
/body
>
<
/html
>
VI-K. L'affichage des articles▲
Nous avons besoin d'une vue pour afficher les articles du blog (resources/views/posts/liste.blade.php) :
Avec ce code :
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.
@extends
('layouts.app'
)
@section
('content'
)
<
div class
=
"
container
"
>
@if
(isset($info
))
<
div class
=
"
row alert alert-info
"
>
{{
$info
}}
<
/div
>
@endif
{!!
$posts
->
links() !!}
@foreach
($posts
as
$post
)
<
article class
=
"
row bg-primary
"
>
<
div class
=
"
col-md-12
"
>
<
header>
<
h1>
{{
$post
->
titre }}
<
/h1
>
<
/header
>
<
hr>
<
section>
<
p>
{{
$post
->
contenu }}
<
/p
>
@if
(auth
()->
check() and
auth
()->
user()->
admin
)
<
form method
=
"
POST
"
action
=
"
{{
route('post.destroy'
,
[
'id'
=>
$post
->
id]
) }}
"
>
{{
method_field('DELETE'
) }}
{{
csrf_field() }}
<
input class
=
"
btn btn-danger btn-xs
"
onclick
=
"
return confirm('Vraiment supprimer cet article ?')
"
type
=
"
submit
"
value
=
"
Supprimer cet article
"
>
<
/form
>
@endif
<
em class
=
"
pull-right
"
>
{{
$post
->
user->
name }}
le {!!
$post
->
created_at->
format('d-m-Y'
) !!}
<
/em
>
<
/section
>
<
/div
>
<
/article
>
<
br>
@endforeach
{!!
$posts
->
links() !!}
<
/div
>
@endsection
Avec cet aspect pour un utilisateur non connecté :
Le lien « Se connecter » ouvre le formulaire de connexion comme on l'a vu dans le chapitre sur l'authentification.
Pour avoir une redirection correcte après la connexion, il faut bien renseigner la propriété $redirectTo dans le contrôleur LoginController :
protected $redirectTo
=
'
post
'
;
La génération des articles dans la vue se fait avec un foreach :
@foreach
($posts
as
$post
)
...
@endforeach
Si vous vous connectez, vous retournez au blog avec deux liens supplémentaires :
On utilise une condition pour adapter les liens :
La méthode guest permet de savoir si un utilisateur est un simple visiteur non authentifié. C'est le contraire de la méthode check qu'on pourrait utiliser en inversant la logique.
Si un administrateur est connecté, il dispose en plus de boutons pour supprimer les articles :
On utilise encore une condition pour détecter un administrateur :
@if
(auth
()->
check() and
auth
()->
user()->
admin
)
...
@endif
Il faut que l'utilisateur soit connecté (check) et que ce soit un administrateur (auth()->user()->admin).
Comme on n'utilise pas laravelcollective/html dans cet exemple (c'est bien aussi de voir comment faire sans ce composant) il faut construire les formulaires avec tout le code nécessaire.
Pour la destruction des articles, on doit aboutir sur cette route :
Il nous faut une méthode DELETE, une méthode qui n'est pas supportée dans les formulaires (tout comme PUT et PATCH). Alors on déclare une méthode POST comme action, mais on ajoute un contrôle caché _method avec la valeur DELETE. À l'arrivée la requête est interprétée comme une requête DELETE. Regardez le code de ce formulaire :
On utilise l'helper method_field pour générer ce code :
<input type
=
"hidden"
name
=
"_method"
value
=
"DELETE"
>
J'ai aussi prévu une simple confirmation en JavaScript avant de supprimer effectivement l'article.
VI-L. La création d'un article▲
Voici maintenant la vue pour le formulaire de création d'un article (resources/views/posts/create.blade.php) :
Avec ce code :
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.
@extends
('layouts.app'
)
@section
('content'
)
<
div class
=
"
col-sm-offset-3 col-sm-6
"
>
<
div class
=
"
panel panel-default
"
>
<
div class
=
"
panel-heading
"
>
Ajout d'un article<
/div
>
<
div class
=
"
panel-body
"
>
<
form method
=
"
POST
"
action
=
"
{{
URL('/post'
) }}
"
>
{{
csrf_field() }}
<
div class
=
"
form-group
{{
$errors
->
has('titre'
) ?
' has-error'
:
''
}}
"
>
<
input class
=
"
form-control
"
placeholder
=
"
Titre
"
name
=
"
titre
"
type
=
"
text
"
value
=
"
{{
old('titre'
) }}
"
autofocus>
@if
($errors
->
has('titre'
))
<
span class
=
"
help-block
"
>
<
strong>
{{
$errors
->
first('titre'
) }}
<
/strong
>
<
/span
>
@endif
<
/div
>
<
div class
=
"
form-group
{{
$errors
->
has('contenu'
) ?
' has-error'
:
''
}}
"
>
<
textarea class
=
"
form-control
"
placeholder
=
"
Contenu
"
name
=
"
contenu
"
cols
=
"
50
"
rows
=
"
10
"
>
{{
old('contenu'
) }}
<
/textarea
>
@if
($errors
->
has('contenu'
))
<
span class
=
"
help-block
"
>
<
strong>
{{
$errors
->
first('contenu'
) }}
<
/strong
>
<
/span
>
@endif
<
/div
>
<
button type
=
"
submit
"
class
=
"
btn btn-primary pull-right
"
>
Envoyer !<
/button
>
<
/form
>
<
/div
>
<
/div
>
<
/div
>
@endsection
Voici l'apparence du formulaire :
Là encore le fait de ne pas utiliser le composant laravellollective/html oblige à :
- écrire tout le code ;
- prévoir de remplir les contrôles avec les anciennes valeurs saisies en cas d'erreur dans la validation.
Cette dernière action se fait en utilisant l'helper old. Par exemple pour le titre on a :
<
input class
=
"
form-control
"
placeholder
=
"
Titre
"
name
=
"
titre
"
type
=
"
text
"
value
=
"
{{
old('titre'
) }}
"
autofocus>
Imaginez que vous ayez saisi le titre, mais pas le contenu, au retour de la validation vous allez retrouver votre titre :
C'est exactement pareil pour le contenu.
VI-M. En résumé▲
- Une relation de type 1:n nécessite la création d'une clé étrangère côté n.
- On peut remplir les tables d'enregistrements avec la population.
- Une relation dans la base nécessite la mise en place de méthodes spéciales dans les modèles.
- Avec les middlewares il est facile de gérer l'accès aux méthodes des contrôleurs.