XVIII. Chapitre 18 : Un blog : les données▲
Il est temps d'utiliser tout ce que nous avons vu dans une application pratique. Il est habituel de présenter un blog dans ce genre de situation, d'une part parce que c'est une application qui permet de brasser pas mal de fonctionnalités, et d'autre part parce que c'est quelque chose de très utile.
Article mis à jour pour la version 4.1.26 de Laravel.
XVIII-A. Installer et configurer Laravel▲
Nous n'allons pas faire quelque chose de très complexe mais de suffisamment structuré pour être significatif. La première chose à faire est d'installer et de configurer Laravel. Nous avons déjà vu comment on fait cela dans les chapitres 2Chapitre 2 : Installation et 3Chapitre 3 : Architecture et configuration. Je pars donc du principe que vous avez une installation toute neuve de Laravel dans un dossier nommé blog auquel on accède avec cet URL : http://localhost/blog/public.
XVIII-A-1. La base de données▲
La structuration des données constitue la colonne vertébrale d'une application. Nous allons donc nous attarder sur ce point. Nous avons besoin pour notre blog de quatre tables : une pour les utilisateurs, une pour les catégories d'articles, une pour les articles et enfin une dernière pour les commentaires. Le but est de bâtir cette configuration :
Voyons de plus près chacune des tables :
- users : nous avons déjà eu affaire à cette table dans le chapitre sur l'authentificationChapitre 15 : Les bases de données 3/3. J'ai juste prévu en plus un champ pour l'email et un autre pour le statut de l'utilisateur, qui est soit administrateur (qui peut créer des articles et gérer le site), soit simple intervenant (qui peut commenter des articles). Les champs created_at et updated_at sont gérés automatiquement par Laravel, on les retrouve dans toutes les tables. Le pseudo et l'email doivent être uniques.
- categories : ici j'ai prévu un titre et une description.
- articles : c'est la table forcément la plus chargée, on trouve un titre, un texte d'introduction intro_text (qui apparaîtra sur la page de la catégorie), un texte de contenu de l'article full_text (qui apparaîtra quand on sélectionnera l'article). J'ai aussi prévu un champ allow_comment pour signaler si on peut ou non commenter l'article. On trouve aussi deux clés étrangères, une qui référence l'utilisateur qui a écrit l'article user_id et une autre qui référence la catégorie categorie_id. Je pars du principe qu'on utilise MySQL avec InnoDB.
- comments : cette table accueillera les commentaires. Là j'ai prévu juste un titre et un champ pour le texte. On trouve aussi deux clés étrangères : une qui référence l'utilisateur qui a écrit le commentaire user_id et une autre qui référence l'article concerné article_id.
- password_reminders : cette table sert pour la réinitialisation du mot de passe.
XVIII-A-2. Créer une migration▲
On va encore utiliser l'outil de migration pour créer ces tables (vous n'êtes évidemment pas obligé de l'utiliser). Commencez par créer une base nommée blog dans MySQL (si vous utilisez un autre serveur ce qui suit reste évidemment valable, par contre il faudra peut-être adapter certains types de données dans les tables). Ensuite créez une migration :
Maintenant installez cette migration :
Vous devez avoir une table migrations dans votre base :
XVIII-A-3. Le schéma des tables▲
Dans le dossier app/database/migrations vous devez avoir un fichier 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.
<?php
use
Illuminate\Database\Schema\Blueprint;
use
Illuminate\Database\Migrations\Migration;
class
CreateBlog extends
Migration {
/**
* Run the migrations.
*
*
@return
void
*/
public
function
up()
{
//
}
/**
* Reverse the migrations.
*
*
@return
void
*/
public
function
down()
{
//
}
}
Il ne nous reste plus qu'à écrire le reste. Nous avons déjà vu ça au chapitre 133Chapitre 12 : Les formulaires. Mais à présent voyons un cas plus réaliste et complet. Voici 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.
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.
<?php
use
Illuminate\Database\Schema\Blueprint;
use
Illuminate\Database\Migrations\Migration;
class
CreateBlog extends
Migration {
public
function
up()
{
Schema::
create('users'
,
function
($table
) {
$table
->
increments('id'
)->
unsigned();
$table
->
string('username'
,
64
)->
unique();
$table
->
string('password'
,
64
);
$table
->
string('email'
,
64
)->
unique();
$table
->
enum('statut'
,
array
('user'
,
'admin'
))->
default
('user'
);
$table
->
string('remember_token'
,
100
)->
nullable();
$table
->
timestamps();
}
);
Schema::
create('categories'
,
function
($table
) {
$table
->
increments('id'
)->
unsigned();
$table
->
string('title'
,
128
)->
unique();
$table
->
text('description'
)->
nullable();
$table
->
timestamps();
}
);
Schema::
create('articles'
,
function
($table
) {
$table
->
increments('id'
)->
unsigned();
$table
->
string('title'
,
128
);
$table
->
integer('user_id'
)->
unsigned();
$table
->
foreign('user_id'
)->
references('id'
)->
on('users'
)->
onDelete('cascade'
)->
onUpdate('cascade'
);
$table
->
integer('categorie_id'
)->
unsigned();
$table
->
foreign('categorie_id'
)->
references('id'
)->
on('categories'
)->
onDelete('cascade'
)->
onUpdate('cascade'
);
$table
->
text('intro_text'
);
$table
->
text('full_text'
);
$table
->
enum('allow_comment'
,
array
('no'
,
'yes'
))->
default
('yes'
);
$table
->
timestamps();
}
);
Schema::
create('comments'
,
function
($table
) {
$table
->
increments('id'
)->
unsigned();
$table
->
string('title'
,
128
);
$table
->
integer('user_id'
)->
unsigned();
$table
->
foreign('user_id'
)->
references('id'
)->
on('users'
)->
onDelete('cascade'
)->
onUpdate('cascade'
);
$table
->
integer('article_id'
)->
unsigned();
$table
->
foreign('article_id'
)->
references('id'
)->
on('articles'
)->
onDelete('cascade'
)->
onUpdate('cascade'
);
$table
->
text('text'
);
$table
->
timestamps();
}
);
}
public
function
down()
{
Schema::
table('articles'
,
function
($table
) {
$table
->
dropForeign('articles_categorie_id_foreign'
);
$table
->
dropForeign('articles_user_id_foreign'
);
}
);
Schema::
table('comments'
,
function
($table
) {
$table
->
dropForeign('comments_user_id_foreign'
);
$table
->
dropForeign('comments_article_id_foreign'
);
}
);
Schema::
drop('categories'
);
Schema::
drop('users'
);
Schema::
drop('comments'
);
Schema::
drop('articles'
);
}
}
Il ne reste plus qu'à lancer la migration :
Vous devez avoir vos tables dans la base :
Pour le détail de la syntaxe du schéma vous pouvez consulter la documentation. Remarquez aussi que pour la suppression des tables, j'ai commencé par supprimer les clés étrangères pour éviter de rencontrer des erreurs dans MySQL.
XVIII-A-4. Des données▲
Nous allons avoir besoin de quelques données pour tester notre site. Nous allons utiliser une fonctionnalité déjà vueChapitre 12 : Les formulaires avec les seeds. Il faut créer un fichier par table, en donnant à chacun un nom judicieux et les placer dans le dossier app/database/seeds. Pour la table users on aura donc le fichier UserTableSeeder.php :
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.
<?php
class
UserTableSeeder extends
Seeder {
public
function
run()
{
DB::
table('users'
)->
insert(
array
(
array
(
'id'
=>
1
,
'username'
=>
'admin'
,
'password'
=>
Hash::
make('admin'
),
'email'
=>
'admin@plop.fr'
,
'statut'
=>
'admin'
,
'created_at'
=>
new
DateTime,
'updated_at'
=>
new
DateTime,
),
array
(
'id'
=>
2
,
'username'
=>
'Dupont'
,
'password'
=>
Hash::
make('dupont'
),
'email'
=>
'dupont@plop.fr'
,
'statut'
=>
'user'
,
'created_at'
=>
new
DateTime,
'updated_at'
=>
new
DateTime,
),
array
(
'id'
=>
3
,
'username'
=>
'Durand'
,
'password'
=>
Hash::
make('durand'
),
'email'
=>
'durand@plop.fr'
,
'statut'
=>
'user'
,
'created_at'
=>
new
DateTime,
'updated_at'
=>
new
DateTime,
)
)
);
}
}
Ici on crée trois utilisateurs dont un administrateur. On crée aussi un fichier CategorieTableSeeder.php pour les catégories :
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.
<?php
class
CategorieTableSeeder extends
Seeder {
public
function
run()
{
DB::
table('categories'
)->
insert(
array
(
array
(
'id'
=>
1
,
'title'
=>
'Catégorie 1'
,
'description'
=>
'blablabla'
,
'created_at'
=>
new
DateTime,
'updated_at'
=>
new
DateTime
),
array
(
'id'
=>
2
,
'title'
=>
'Catégorie 2'
,
'description'
=>
'blablabla'
,
'created_at'
=>
new
DateTime,
'updated_at'
=>
new
DateTime
),
array
(
'id'
=>
3
,
'title'
=>
'Catégorie 3'
,
'description'
=>
'blablabla'
,
'created_at'
=>
new
DateTime,
'updated_at'
=>
new
DateTime
),
array
(
'id'
=>
4
,
'title'
=>
'Catégorie 4'
,
'description'
=>
'blablabla'
,
'created_at'
=>
new
DateTime,
'updated_at'
=>
new
DateTime
)
)
);
}
}
On crée aussi un fichier ArticleTableSeeder.php pour les articles :
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.
<?php
class
ArticleTableSeeder extends
Seeder {
public
function
run()
{
DB::
table('articles'
)->
insert(
array
(
array
(
'id'
=>
1
,
'title'
=>
'Article 1'
,
'user_id'
=>
'1'
,
'categorie_id'
=>
'1'
,
'intro_text'
=>
'Intro 1'
,
'full_text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-01 00:00:00'
,
'updated_at'
=>
'2013-02-01 00:00:00'
),
array
(
'id'
=>
2
,
'title'
=>
'Article 2'
,
'user_id'
=>
'1'
,
'categorie_id'
=>
'1'
,
'intro_text'
=>
'Intro 2'
,
'full_text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-02 00:00:00'
,
'updated_at'
=>
'2013-02-02 00:00:00'
),
array
(
'id'
=>
3
,
'title'
=>
'Article 3'
,
'user_id'
=>
'1'
,
'categorie_id'
=>
'2'
,
'intro_text'
=>
'Intro 3'
,
'full_text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-06 00:00:00'
,
'updated_at'
=>
'2013-02-06 00:00:00'
),
array
(
'id'
=>
4
,
'title'
=>
'Article 4'
,
'user_id'
=>
'1'
,
'categorie_id'
=>
'2'
,
'intro_text'
=>
'Intro 4'
,
'full_text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-05 00:00:00'
,
'updated_at'
=>
'2013-02-05 00:00:00'
),
array
(
'id'
=>
5
,
'title'
=>
'Article 5'
,
'user_id'
=>
'1'
,
'categorie_id'
=>
'3'
,
'intro_text'
=>
'Intro 5'
,
'full_text'
=>
'blablabla'
,
'created_at'
=>
'2013-01-01 00:00:00'
,
'updated_at'
=>
'2013-01-01 00:00:00'
),
array
(
'id'
=>
6
,
'title'
=>
'Article 6'
,
'user_id'
=>
'1'
,
'categorie_id'
=>
'4'
,
'intro_text'
=>
'Intro 6'
,
'full_text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-04 00:00:00'
,
'updated_at'
=>
'2013-02-04 00:00:00'
)
)
);
}
}
Et on crée un dernier pour les commentaires CommentTableSeeder.php :
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.
<?php
class
CommentTableSeeder extends
Seeder {
public
function
run()
{
DB::
table('comments'
)->
insert(
array
(
array
(
'id'
=>
1
,
'title'
=>
'Commentaire 1'
,
'user_id'
=>
'2'
,
'article_id'
=>
'1'
,
'text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-01 00:00:00'
,
'updated_at'
=>
'2013-02-01 00:00:00'
),
array
(
'id'
=>
2
,
'title'
=>
'Commentaire 2'
,
'user_id'
=>
'2'
,
'article_id'
=>
'1'
,
'text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-01 00:00:00'
,
'updated_at'
=>
'2013-02-01 00:00:00'
),
array
(
'id'
=>
3
,
'title'
=>
'Commentaire 3'
,
'user_id'
=>
'3'
,
'article_id'
=>
'3'
,
'text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-01 00:00:00'
,
'updated_at'
=>
'2013-02-01 00:00:00'
),
array
(
'id'
=>
4
,
'title'
=>
'Commentaire 4'
,
'user_id'
=>
'2'
,
'article_id'
=>
'4'
,
'text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-01 00:00:00'
,
'updated_at'
=>
'2013-02-01 00:00:00'
),
array
(
'id'
=>
5
,
'title'
=>
'Commentaire 5'
,
'user_id'
=>
'3'
,
'article_id'
=>
'4'
,
'text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-01 00:00:00'
,
'updated_at'
=>
'2013-02-01 00:00:00'
),
array
(
'id'
=>
6
,
'title'
=>
'Commentaire 6'
,
'user_id'
=>
'3'
,
'article_id'
=>
'5'
,
'text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-01 00:00:00'
,
'updated_at'
=>
'2013-02-01 00:00:00'
),
array
(
'id'
=>
7
,
'title'
=>
'Commentaire 7'
,
'user_id'
=>
'2'
,
'article_id'
=>
'1'
,
'text'
=>
'blablabla'
,
'created_at'
=>
'2013-02-01 00:00:00'
,
'updated_at'
=>
'2013-02-01 00:00:00'
)
)
);
}
}
Et il faut renseigner le fichier DatabaseSeeder.php en appelant nos classes dans le bon ordre pour ne pas rencontrer des exceptions de la part de MySQL :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
<?php
class
DatabaseSeeder extends
Seeder {
public
function
run()
{
$this
->
call('UserTableSeeder'
);
$this
->
call('CategorieTableSeeder'
);
$this
->
call('ArticleTableSeeder'
);
$this
->
call('CommentTableSeeder'
);
}
}
Il ne reste plus qu'à utiliser Artisan :
XVIII-A-5. Les modèles▲
Nous avons vu en étudiant l'authentificationChapitre 15 : Les bases de données 3/3 que Laravel possède déjà un modèle pour les utilisateurs. Il nous faut compléter ce modèle pour faire fonctionner nos relations. Voici donc le fichier app/models/User 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.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
<?php
use
Illuminate\Auth\UserInterface;
use
Illuminate\Auth\Reminders\RemindableInterface;
class
User extends
Eloquent implements
UserInterface, RemindableInterface {
protected
$table
=
'users'
;
protected
$hidden
=
array
('password'
);
public
function
getAuthIdentifier()
{
return
$this
->
getKey();
}
public
function
getAuthPassword()
{
return
$this
->
password;
}
public
function
getRememberToken()
{
return
$this
->
remember_token;
}
public
function
setRememberToken($value
)
{
$this
->
remember_token =
$value
;
}
public
function
getRememberTokenName()
{
return
'remember_token'
;
}
public
function
getReminderEmail()
{
return
$this
->
email;
}
public
function
articles()
{
return
$this
->
hasMany('Article'
);
}
public
function
comments()
{
return
$this
->
hasMany('Comment'
);
}
}
Nous avons également besoin d'un modèle pour les catégories (app/models/Categorie) :
2.
3.
4.
5.
6.
7.
class Categorie extends
Eloquent {
public
function
articles()
{
return
$this
->
hasMany('Article'
);
}
}
Un autre pour les articles (app/models/Article) :
Et un dernier modèle pour les commentaires (app/models/Comment) :
XVIII-B. Migration pour l'authentification▲
On peut créer automatiquement une migration en utilisant Artisan :
Ce qui a pour effet de créer la migration :
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.
<?php
use
Illuminate\Database\Schema\Blueprint;
use
Illuminate\Database\Migrations\Migration;
class
CreatePasswordRemindersTable extends
Migration {
/**
* Run the migrations.
*
*
@return
void
*/
public
function
up()
{
Schema::
create('password_reminders'
,
function
(Blueprint $table
)
{
$table
->
string('email'
)->
index();
$table
->
string('token'
)->
index();
$table
->
timestamp('created_at'
);
}
);
}
/**
* Reverse the migrations.
*
*
@return
void
*/
public
function
down()
{
Schema::
drop('password_reminders'
);
}
}
Il nous faut finalement lancer la migration pour créer cette table dans la base :
Nous avons à présent tout ce qui nous est nécessaire pour stocker et manipuler les données. La prochaine étape sera de définir l'aspect du blog.