I. Migrations et modèles▲
À partir de ce chapitre, il serait souhaitable que vous installiez une barre de débogage. La plus utile est celle proposée par barryvdh. Suivez les indications fournies pour l'installation, ça vous fera un bon exercice.
I-A. Les migrations▲
Une migration permet de créer et de mettre à jour un schéma de base de données. Autrement dit, vous pouvez créer des tables, des colonnes dans ces tables, en supprimer, créer des index… Tout ce qui concerne la maintenance de vos tables peut être pris en charge par cet outil. Vous avez ainsi un suivi de vos modifications.
I-A-1. La configuration de la base▲
Vous devez dans un premier temps avoir une base de données. Laravel permet de gérer les bases de type MySQL, Postgres, SQLite et SQL Server. Je ferai tous les exemples avec MySQL, mais le code sera aussi valable pour les autres types de bases.
Il faut indiquer où se trouve votre base, son nom, le nom de l'utilisateur, le mot de passe dans le fichier de configuration .env :
2.
3.
4.
5.
6.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
Ici nous avons les valeurs par défaut à l'installation de Laravel.
Voici par exemple mes réglages pour ma base de test MySQL nommée « tuto » avec MySQL en local non sécurisé :
DB_DATABASE=tuto
DB_USERNAME=root
DB_PASSWORD=
I-A-2. Artisan▲
Nous avons déjà utilisé Artisan qui permet de faire beaucoup de choses, vous avez un aperçu des commandes en entrant :
php artisan
Vous avez une longue liste. Pour ce chapitre nous allons nous intéresser uniquement à celles qui concernent les migrations :
2.
3.
4.
5.
6.
7.
8.
Migrate Run the database migrations
...
migrate
migrate:install Create the migration repository
migrate:refresh Reset and re-run all migrations
migrate:reset Rollback all database migrations
migrate:rollback Rollback the last database migration
migrate:status Show the status of each migration
I-A-3. Créer la migration▲
On va créer la migration pour notre table :
php artisan make:migration create_emails_table
Si vous regardez maintenant dans le dossier database/migrations, vous trouvez un fichier du genre 2016_08_31_212841_create_emails_table.php (la partie numérique qui inclut la date sera évidemment différente pour vous) :
Mais il y a déjà des migrations présentes, à quoi servent-elles ?
Il y a déjà effectivement deux migrations présentes :
- table users : c'est une migration de base pour créer une table des utilisateurs ;
- table password_resets : c'est une migration liée à la précédente qui permet de gérer le renouvellement des mots de passe en toute sécurité.
Nous nous intéresserons à ces migrations dans un chapitre ultérieur. Comme nous n'allons pas avoir besoin immédiatement de ces migrations, le mieux est de les supprimer pour le moment pour éviter de créer des tables inutiles :
Voici le contenu de la migration que nous venons de créer :
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.
<?php
use
Illuminate\Support\Facades\Schema;
use
Illuminate\Database\Schema\Blueprint;
use
Illuminate\Database\Migrations\Migration;
class
CreateEmailsTable extends
Migration
{
/**
* Run the migrations.
*
*
@return
void
*/
public
function
up()
{
//
}
/**
* Reverse the migrations.
*
*
@return
void
*/
public
function
down()
{
//
}
}
On dispose dans cette classe de deux fonctions :
- up : ici on mettra le code de création ;
- down : ici on mettra le code de suppression.
On veut créer une table « emails » avec :
- un id autoincrémenté ;
- un champ « email » de type texte ;
- des champs pour mémoriser les dates de création et de modification.
Voilà le code correspondant :
On demande au constructeur de schéma (Schema) de créer (create) la table « emails ». Dans la fonction anonyme, on définit ce qu'on veut pour la table :
- une colonne « id » auto-incrémentée qui sera ainsi la clé primaire de la table ;
- une colonne « email » de type string ;
- deux colonnes pour les dates (générées par la méthode timestamps).
Pour la méthode down on va juste supprimer la table avec un drop :
Notre migration est maintenant créée.
I-A-4. Utiliser la migration▲
On va maintenant utiliser la migration (méthode up de la migration) :
Si on regarde maintenant dans la base, on trouve la table « emails » avec ces quatre colonnes :
Vous avez aussi la création d'une table migrations :
Cette table sert à l'intendance des migrations et vous ne devez pas y toucher.
Si vous avez fait une erreur, vous pouvez revenir en arrière avec un rollback qui annule la dernière migration effectuée (utilisation de la méthode down de la migration) :
La table a maintenant été supprimée de la base. Comme on va avoir besoin de cette table, on relance la migration.
On peut aussi effectuer un rafraîchissement de toutes les migrations avec la commande refresh (rollback de toutes les migrations et nouveau lancement de toutes les migrations).
Vous disposez également de la commande status pour savoir où vous en êtes :
Mais pour le moment avec une seule migration, il n'y a vraiment pas de quoi se perdre.
I-B. Eloquent et le modèle▲
Laravel propose un ORM (acronyme de object-relational mapping ou en bon français un mappage objet-relationnel) très performant.
De quoi s'agit-il ?
Tout simplement que tous les éléments de la base de données ont une représentation sous forme d'objets manipulables.
Quel intérêt ?
Tout simplement de simplifier grandement les opérations sur la base comme nous allons le voir dans toute cette partie du cours.
Avec Eloquent une table est représentée par une classe qui étend la classe Model. Pour notre table emails, on va à nouveau utiliser Artisan pour la création du modèle :
php artisan make:model Email
On trouve le fichier ici :
Avec cette trame de base :
2.
3.
4.
5.
6.
7.
8.
9.
10.
<?php
namespace
App;
use
Illuminate\Database\Eloquent\Model;
class
Email extends
Model
{
//
}
On va se contenter de ce code pour notre exemple.
On peut créer le modèle en même temps que la migration pour la table avec cette syntaxe :
php artisan make:model Email -m
Nous allons voir maintenant comment utiliser cette classe en construisant notre petite application.
I-C. La validation▲
Pour la validation on va encore créer une requête de formulaire :
php artisan make:request EmailRequest
On trouve la requête dans son dossier :
La voici avec le code complété :
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.
<?php
namespace
App\Http\Requests;
use
Illuminate\Foundation\Http\FormRequest;
class
EmailRequest 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
[
'email'
=>
'bail|required|email|unique:emails'
];
}
}
On a quatre règles :
- bail : on s'arrête à la première erreur ;
- required : le champ est requis ;
- email : on doit avoir une adresse email valide ;
- unique : l'email ne doit pas déjà exister (unique) dans la table emails (on sous-entend qu'il s'agit de la colonne email).
Remarquez la puissance de la dernière règle : Eloquent va vérifier que notre email n'existe pas déjà dans la table !
I-D. Les routes et le contrôleur▲
I-D-1. Routes▲
On va avoir deux routes :
Route::
get('
email
'
,
'
EmailController@create
'
);
Route::
post('
email
'
,
'
EmailController@store
'
)->
name('
store.email
'
);
Remarquez que la seconde route est nommée (store.email).
L'URL de base sera : http://monsite.fr/email
I-D-2. Contrôleur▲
On crée le contrôleur avec Artisan :
php artisan make:controller EmailController
On le trouve dans son dossier :
Le code du contrôleur va reprendre l'essentiel de ce que nous avons vu dans les chapitres précédents en utilisant à nouveau la validation injectée. 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.
<?php
namespace
App\Http\Controllers;
use
App\Email;
use
App\Http\Requests\EmailRequest;
class
EmailController extends
Controller
{
public
function
create()
{
return
view('email'
);
}
public
function
store(EmailRequest $request
)
{
$email
=
new
Email;
$email
->
email =
$request
->
email;
$email
->
save();
return
view('email_ok'
);
}
}
La nouveauté réside uniquement dans l'utilisation du modèle :
Ici on crée une nouvelle instance de Email. On affecte l'attribut email avec la valeur de l'entrée. Enfin on demande au modèle d'enregistrer cette ligne effectivement dans la table (save).
On ne se soucie pas des colonnes des dates (created_at et updated_at) qui sont automatiquement renseignées par Eloquent.
I-E. Les vues▲
On va utiliser le même tempate que dans les précédents chapitres (resources/views/template.blade.php) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
<!
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
"
>
<
title>
Les emails<
/title
>
{!!
Html::
style('<a href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css</a>'
) !!}
{!!
Html::
style('<a href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css">https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css</a>'
) !!}
<!--[if lt IE 9]>
{{
Html::
style('<a href="https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js">https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js</a>'
) }}
{{
Html::
style('<a href="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js">https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js</a>'
) }}
<![endif]-->
<
/head
>
<
body>
@yield
('contenu'
)
<
/body
>
<
/html
>
Voici la vue pour le formulaire(resources/views/email.blade.php) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
@extends
('template'
)
@section
('contenu'
)
<
br>
<
div class
=
"
col-sm-offset-4 col-sm-4
"
>
<
div class
=
"
panel panel-info
"
>
<
div class
=
"
panel-heading
"
>
Inscription à la lettre d'information<
/div
>
<
div class
=
"
panel-body
"
>
{!!
Form::
open([
'route'
=>
'store.email'
]
) !!}
<
div class
=
"
form-group
{!!
$errors
->
has('email'
) ?
'has-error'
:
''
!!}
"
>
{!!
Form::
email('email'
,
null
,
array
('class'
=>
'form-control'
,
'placeholder'
=>
'Entrez votre email'
)) !!}
{!!
$errors
->
first('email'
,
'<small class="help-block">:message</small>'
) !!}
<
/div
>
{!!
Form::
submit('Envoyer !'
,
[
'class'
=>
'btn btn-info pull-right'
]
) !!}
{!!
Form::
close() !!}
<
/div
>
<
/div
>
<
/div
>
@endsection
Cette vue ne présente aucune nouveauté pour vous si ce n'est l'utilisation du nom de la route, elle répond à l'URL (avec le verbe get) : http://monsite.fr/email.
L'aspect est le suivant :
Voici maintenant la vue de confirmation (resources/views/email_ok.blade.php) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
@extends
('template'
)
@section
('contenu'
)
<
br>
<
div class
=
"
col-sm-offset-3 col-sm-6
"
>
<
div class
=
"
panel panel-info
"
>
<
div class
=
"
panel-heading
"
>
Inscription à la lettre d'information<
/div
>
<
div class
=
"
panel-body
"
>
Merci. Votre adresse a bien été prise en compte.
<
/div
>
<
/div
>
<
/div
>
@endsection
Avec cet aspect :
I-F. Le fonctionnement▲
Voyons maintenant si tout se passe bien. Je soumets une adresse :
Je reçois la confirmation :
Je regarde dans la base :
Je soumets la même adresse :
Voyons un peu les requêtes générées par Eloquent avec par exemple la soumission de l'adresse toto@gui.com (vous les trouvez à la rubrique Queries de la barre de débogage) :
select
count
(*)
as
aggregate
from
`emails`
where
`email`
=
'toto@gui.com'
insert
into
`emails`
(
`email`
)
values
(
'toto@gui.com'
)
La première requête est destinée à tester la présence éventuelle de l'adresse dans la table pour répondre à la règle « unique ». La seconde insère l'enregistrement dans la table. Vous voyez qu'Eloquent vous simplifie la tâche, vous n'avez pas besoin d'écrire les requêtes SQL, il le fait pour vous. Vous vous contentez de manipuler un objet.
N'hésitez pas à regarder les informations de la barre de débogage, vous y trouverez de précieux renseignements sur les requêtes (HTTP et SQL), les vues utilisées, les routes, les délais, les exceptions générées… Vous avez aussi un historique en cliquant sur la petite image de dossier :
I-G. Organisation du code▲
Maintenant posons-nous à nouveau la question de l'organisation du code. Dans le contrôleur nous avons mis la gestion du modèle :
Autrement dit nous avons lié de façon étroite le contrôleur et le modèle. Supposons que nous fassions des modifications dans notre base de données et que nous placions l'email dans une autre table. Nous devrions évidemment intervenir dans le code du contrôleur pour tenir compte de cette modification.
Vous pouvez évidemment considérer que c'est peu probable, que la modification du code n'est pas très importante… Mais ici on a une application très simple, dans une situation réelle les utilisations de modèles sont nombreuses et alors la question devient bien plus pertinente.
I-G-1. Première version▲
Dans ce cours je m'efforce de vous entraîner à prendre de bonnes habitudes. Plutôt que d'instancier directement une classe dans une autre, il vaut mieux une injection et laisser faire le conteneur. Regardez cette nouvelle version du contrôleur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
<?php
namespace
App\Http\Controllers;
use
App\Email;
use
App\Http\Requests\EmailRequest;
class
EmailController extends
Controller
{
public
function
create()
{
return
view('email'
);
}
public
function
store(EmailRequest $request
,
Email $email
)
{
$email
->
email =
$request
->
email;
$email
->
save();
return
view('email_ok'
);
}
}
Maintenant le modèle est injecté dans la méthode, c'est plus élégant et efficace. Si jamais vous changez de modèle, vous n'avez plus qu'un changement de code limité sur le contrôleur. Mais ce n'est pas encore parfait.
I-G-2. Seconde version▲
Dans l'idéal on veut que notre contrôleur ne soit pas du tout concerné par un changement dans la gestion des modèles. Voici une façon de procéder :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<?php
namespace
App\Http\Controllers;
use
App\Http\Requests\EmailRequest;
use
App\Repositories\EmailRepository;
class
EmailController extends
Controller
{
public
function
create()
{
return
view('email'
);
}
public
function
store(EmailRequest $request
,
EmailRepository $emailRepository
)
{
$emailRepository
->
save($request
->
email);
return
view('email_ok'
);
}
}
Maintenant j'injecte une classe de gestion qui possède la méthode save. Voici le contrat avec une interface (app/Repositories/EmailRepositoryInterface) :
2.
3.
4.
5.
6.
7.
8.
<?php
namespace
App\Repositories;
interface
EmailRepositoryInterface
{
public
function
save($mail
);
}
Et voici la classe qui implémente cette interface (app/Repositories/EmailRepository) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<?php
namespace
App\Repositories;
use
App\Email;
class
EmailRepository implements
EmailRepositoryInterface
{
protected
$email
;
public
function
__construct
(Email $email
)
{
$this
->
email =
$email
;
}
public
function
save($mail
)
{
$this
->
email->
email =
$mail
;
$this
->
email->
save();
}
}
Le modèle est injecté dans cette classe. Je l'ai injecté dans le constructeur pour généraliser la démarche en imaginant qu'on créera d'autres méthodes que l'on peut regrouper ici pour gérer les enregistrements. Le code est maintenant parfaitement organisé, facile à modifier et à tester.
Le Design Pattern Repository est un des plus répandus. Il permet de gérer la persistance des informations.
I-G-3. Troisième version▲
On peut enfin, comme on l'a déjà vu, référencer l'interface plutôt que la classe, mais dans ce cas il faut informer le conteneur de la dépendance. Modifiez ainsi le fichier app/Http/Providers/AppServiceProvider.php :
Et le contrôleur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<?php
namespace
App\Http\Controllers;
use
App\Http\Requests\EmailRequest;
use
App\Repositories\EmailRepositoryInterface;
class
EmailController extends
Controller
{
public
function
create()
{
return
view('email'
);
}
public
function
store(EmailRequest $request
,
EmailRepositoryInterface $emailRepository
)
{
$emailRepository
->
save($request
->
email);
return
view('email_ok'
);
}
}
Maintenant, étant donné que le conteneur sait quelle classe instancier à partir de l'interface passée en paramètre, vous avez un code propre et facile à maintenir et à tester. Si vous changez d'avis sur la manière de stocker les emails, il vous suffit de décider d'instancier une autre classe à partir de l'interface, tant que le contrat passé avec le contrôleur ne change pas !
I-H. En résumé▲
- La base de données doit être configurée pour fonctionner avec Laravel.
- Les migrations permettent d'intervenir sur le schéma des tables de la base.
- Eloquent permet une représentation des tables sous forme d'objets pour simplifier les manipulations des enregistrements.
- Il est judicieux de prévoir la gestion du modèle dans une classe injectée dans le contrôleur.
- La barre de débogage donne de précieux renseignements sur les requêtes.