Laravel 4 : 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.
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 2 et 3. Je pars donc du principe que vous avez une installation toute neuve de Laravel dans un dossier nommé blog auquel on accède avec cette URL : http://localhost/blog/public.
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 4 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’authentification. 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électionne l’article). J’ai aussi prévu un champ allow_comment pour signaler si on peut ou pas 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.
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 :
Le schéma des tables
Dans le dossier app/database/migrations vous devez avoir un fichier avec ce code :
<?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 13. Mais à présent nous avons un cas plus réaliste et complet. Voici le code :
<?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.
Des données
Nous allons avoir besoin de quelques données pour tester notre site. Nous allons utiliser une fonctionnalité déjà vue avec les seeds. Il faut créer un fichier par table en donnant à celui-ci un nom judicieux et le placer dans le dossier app/database/seeds. Pour la table users on aura donc le fichier UserTableSeeder.php :
<?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 3 utilisateurs dont un administrateur. On crée aussi un fichier CategorieTableSeeder.php pour les catégories :
<?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 ) ) ); } }
Un fichier ArticleTableSeeder.php pour les articles :
<?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 un dernier pour les commentaires CommentTableSeeder.php :
<?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 :
<?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 :
Les modèles
Nous avons vu en étudiant l’authentification 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é :
<?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):
class Categorie extends Eloquent { public function articles() { return $this->hasMany('Article'); } }
Un autre pour les articles (app/models/Article):
class Article extends Eloquent { public function comments() { return $this->hasMany('Comment'); } public function categorie() { return $this->belongsTo('Categorie'); } public function user() { return $this->belongsTo('User'); } }
Et un dernier pour les commentaires (app/models/Comment):
class Comment extends Eloquent { public function article() { return $this->belongsTo('Article'); } public function user() { return $this->belongsTo('User'); } }
Migration pour l’authentification
Comme suite au commentaire judicieux de chDUP j’avais effectivement oublié de mentionner la migration pour l’authentification concernant le mot de passe. On peut la créer automatiquement en utilisant Artisan :
Ce qui a pour effet de créer la migration :
Avec ce code :
<?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.
11 commentaires
ChDUP
bonjour bestmomo 🙂
J’ai un problème avec l’oubli du mot de passe.
erreur : « Base table or view not found: 1146 Table ‘laravel.password_reminders’ doesn’t exist »
Je suppose qu’il faut créer cette table password_reminders pour les mot de passe en attente de réinitialisation mais je ne vois pas à quel moment tu la crée, dans ce tuto ?
merci
bestmomo
Oui tu as raison j’ai pas mis la migration, je l’ajoute 😉
Saradimi
Salut et merci pour ce tuto !
J’ai un petit souci quand je lance l’insertion des données, j’obtient l’erreur suivante :
[ErrorException] Object of class DateTime could not be converted to string
Merci par avance pour ton aide
bestmomo
Salut, je pense que tu dois avoir une erreur de frappe quelques part dans ton code. Essaie de lancer les seed un par un pour localiser. Ensuite pour avoir la « vraie » erreur commente les lignes où tu as des new DateTime.
Saradimi
Truc de fou !!! J’ai supprimé tous les « new DateTime », je les ai retapé et là, ça à marché 😀
Merci pour ton retour !
wistar
Lorsque je seed les données, j’ai une erreur parce que Laravel ne lit pas l’accent aïgu de Catégorie. Il lit donc tous les catégories (1, 2, 3 et 4) comme Cat. Comme le titre est unique, une erreur apparaît.
Une idée comment faire en sorte que je puisse seed des données avec des accents?
bestmomo
Salut,
Dans ton fichier de configuration pour la base tu as mis quoi comme collation et charset ? Est-ce que ça correspond à ce que tu as dans ta table ?
wistar
Salut,
Ma collation de base de donnée est utf8_unicode_ci. Pour mon database.php c’est charset utf8 et collation utf8_unicode_ci. Est-ce que je devrais essayer une autre collation ou le problème est ailleurs?
wistar
En fait, j’ai aussi un problème d’accent avec le flash_notice du chapitre suivant. Mon « résultat de recherche » s’affiche « r�sultat de recherche ».
bestmomo
Je ne pense pas que le souci vienne de Laravel mais plutôt de ton PHP. Tu testes en local ? Tu as essayé sur un autre serveur ?
pegasus
Salut ,
Changer l’encodage de ton fichier CategorieTableSeeder.php en UTF-8