Article mis à jour le 26/10/2015
Cet article initie une nouvelle série sur la version 5 de Laravel qui va décrire le processus de création d'une application en permettant ainsi de voir les principales étapes et les éléments essentiels de ce framework.
Ce n'est pas à proprement parler une initiation progressive mais plutôt une approche transversale permettant d'ordonner les connaissances et de les voir en œuvre dans une application concrète.
En parallèle je publierai une série d'initiation sur le site Openclassrooms. Le cours est pratiquement terminé mais il va falloir attendre un peu pour sa publication pour des raisons d'organisation propres à ce site. Il paraîtra avec des activités et la possibilité de faire valider ses connaissances.
L'application que je vous propose de réaliser existe déjà et est disponible sur Github. Il vous est donc possible de la charger et la tester. La procédure d'installation est décrite et ne devrait présenter aucune difficulté. Je vous conseille d'ailleurs de commencer par là et de parcourir l'application pour en découvrir les possibilités. Globalement c'est un système de blog avec authentification, rôles, gestion des utilisateurs, gestion des médias... C'est à dire quelque chose d'assez classique mais de suffisamment approfondi pour révéler les principaux aspects du framework.
Mon parti-pris a été de n'utiliser aucun package supplémentaire autre que celui qui permet de faciliter l'écriture des vues pour les formulaires. Ce package était d'ailleurs inclus dans la précédente version de Laravel. Il a été exclu de la nouvelle version parce que celle-ci se présente comme versatile et pas forcément destinée à gérer un "frontend".
Dans cet article je vais commencer par présenter la gestion de données mise en place pour l'application.
Le schéma
L'établissement du schéma des données est certainement la première chose à faire lorsqu'on crée une application.
Voici le schéma global de la base de données : Il comporte 8 tables :- users : table des utilisateurs :
- username : le pseudo
- email : l'email
- password : mot de passe
- seen : un booléen pour savoir si le nouvel utilisateur a été vu par l'administrateur
- valid : un booléen pour savoir si l'utilisateur a été validé pour laisser des commentaires
- role_id : c'est la clé étrangère pour connaître le rôle de l'utilisateur
- confirmed : un booléen pour la confirmation de l'adresse email
- confirmation_code : un code pour authentifier la confirmation de l'adresse email
- roles : table des rôles des utilisateurs :
- title : titre du rôle tel qu'il s'affiche
- slug : titre du rôle tel qu'il est utilisé dans le code
- password_reset : table spécifique de l'authentification pour la réinitialisation des mots de passe
- contacts : table pour mémoriser les messages des visiteurs :
- name : nom
- email : email
- text : message
- posts : table des articles :
- title : titre
- slug : titre formaté pour être inclus dans les urls
- summary : sommaire
- content : contenu
- seen : un booléen pour savoir si le nouvel article a été vu par l'administrateur
- active : un booléen pour savoir si l'article est publié
- user_id : c'est la clé étrangère pour connaître l'auteur de l'article
- comments : table des commentaires des articles :
- content : le commentaire
- seen : un booléen pour savoir si le nouveau commentaire a été vu par l'administrateur
- user_id : c'est la clé étrangère pour connaître l'auteur du commentaire
- post_id : c'est la clé étrangère pour connaître l'article associé
- tags : table des mots clés pour les articles :
- tag : mot clé
- post_tags : table pivot pour relier les tags aux articles :
- post_id : c'est la clé étrangère pour connaître l'article associé
- tag_id : c'est la clé étrangère pour connaître le tag associé
Évidemment les données auraient pu être organisées de bien d'autres manières mais j'ai voulu quelque chose de relativement simple et cohérent.
En particulier j'aurais pu créer un table spécifique pour regrouper les champs seen. Il aurait alors fallu prévoir une relation polymorphique avec les tables concernées. Le tout aurait été sans doute plus "propre" mais aurait un peu compliqué la gestion.
Migrations et population
Les migrations
Laravel utilise un outil de migration qui simplifie les opérations sur la base de données et permet de versioning. Les migrations se trouvent dans le dossier database/migrations :Classiquement on crée un fichier de migration par opération élémentaire : création d'une table, modification d'une table, création d'un index...
Ici j'ai créé autant de migrations que de tables. D'autre part il y a un fichier spécifique pour la clé étrangère des utilisateurs.
Voyons un de ces fichiers, par exemple celui pour la création de la table des articles :<?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreatePostsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('posts', function(Blueprint $table) { $table->increments('id'); $table->timestamps(); $table->string('title', 255); $table->string('slug', 255)->unique(); $table->text('summary'); $table->text('content'); $table->boolean('seen')->default(false); $table->boolean('active')->default(false); $table->integer('user_id')->unsigned(); }); Schema::table('posts', function(Blueprint $table) { $table->foreign('user_id')->references('id')->on('users') ->onDelete('restrict') ->onUpdate('restrict'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('posts', function(Blueprint $table) { $table->dropForeign('posts_user_id_foreign'); }); Schema::drop('posts'); } }
La fonction up est destinée à créer quelque chose dans la base, la fonction down est destinée à faire l'opération inverse.
Lorsqu'on effectue la commande php artisan migrate la fonction up est appelée. La table posts est créée dans la base avec tous les champs décris :
Schema::create('posts', function(Blueprint $table) { $table->increments('id'); $table->timestamps(); $table->string('title', 255); $table->string('slug', 255)->unique(); $table->text('summary'); $table->text('content'); $table->boolean('seen')->default(false); $table->boolean('active')->default(false); $table->integer('user_id')->unsigned(); });Est aussi déclarée la clé étrangère :
Schema::table('posts', function(Blueprint $table) { $table->foreign('user_id')->references('id')->on('users') ->onDelete('restrict') ->onUpdate('restrict'); });
La population
Pour que l'application fonctionne on a besoin d'enregistrements. C'est le travail de la population (seed) d'en créer. Le fichier correspondant est placé dans le dossier database/seeds :
Je n'insiste pas sur cet aspect un peu accessoire. Vous pouvez aller voir dans le fichier comment c'est réalisé.
On appelle ce fichier avec la commande php artisan db:seed.
Il est possible de faire d'un seul coup la migration et la population avec la commande php artisan migrate --seed.
Les relations
Il y a un certain nombre de relations entre les tables.Has Many
On a une relation has many entre les utilisateurs et les articles :C'est la clé étrangère user_id dans la table posts qui crée la relation.
Il y a aussi une relation has many entre les utilisateurs et les commentaires :
C'est la clé étrangère user_id dans la table comments qui crée la relation.
On a une troisième relation has many entre les rôles et les utilisateurs :
C'est la clé étrangère role_id dans la table users qui crée la relation.
On a une dernière relation has many entre les articles et les commentaires :
C'est la clé étrangère post_id dans la table comments qui crée la relation.
On trouve dans les modèles du côté "1" de la relation la fonction hasMany. Par exemple dans le modèle User :
/** * One to Many relation * * @return Illuminate\Database\Eloquent\Relations\hasMany */ public function posts() { return $this->hasMany('App\Models\Post'); } /** * One to Many relation * * @return Illuminate\Database\Eloquent\Relations\hasMany */ public function comments() { return $this->hasMany('App\Models\Comment'); }On trouve dans les modèles du côté "n" de la relation la fonction belongsTo. Comme par exemple dans le modèle Comment :
/** * One to Many relation * * @return Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { return $this->belongsTo('App\Models\User'); } /** * One to Many relation * * @return Illuminate\Database\Eloquent\Relations\BelongsTo */ public function post() { return $this->belongsTo('App\Models\Post'); }
Belongs to Many
On a une relation belongs to many entre les articles et les tags :On trouve dans la table pivot post_tag les deux clés étrangères post_id et tag_id.
On a dans les deux modèles impliqués dans la relation la fonction belongsToMany, comme par exemple dans le modèle Post :
/** * Many to Many relation * * @return Illuminate\Database\Eloquent\Relations\belongToMany */ public function tags() { return $this->belongsToMany('App\Models\Tag'); }
Les rôles
Il est prévu 3 rôles dans la table roles :L'administrateur a tous les droits dans l'application, le rédacteur peut rédiger des articles, l'utilisateur ne peut qu'ajouter des commentaires aux articles.
Dans le modèle User figurent deux fonctions qui ont pour objet de déterminer le rôle de l'utilisateur connecté :
/** * Check admin role * * @return bool */ public function isAdmin() { return $this->role->slug == 'admin'; } /** * Check not user role * * @return bool */ public function isNotUser() { return $this->role->slug != 'user'; }
La première fonction détermine si c'est un administrateur et la seconde que ce n'est pas un simple utilisateur (dans ce cas c'est donc soit un administrateur, soit un rédacteur). On aura besoin de ces méthodes dans plusieurs endroits de l'application.
Le presenter
Vous trouverez dans les modèles Comment, Contact et Post la référence du trait DatePresenter. Par exemple dans le modèle Comment :
class Comment extends Model { use DatePresenter;
Vous trouvez ce trait dans le dossier app/Presenters :
<?php namespace App\Presenters; use Carbon\Carbon; trait DatePresenter { /** * Format created_at attribute * * @param Carbon $date * @return string */ public function getCreatedAtAttribute($date) { return $this->getDateFormated($date); } /** * Format updated_at attribute * * @param Carbon $date * @return string */ public function getUpdatedAtAttribute($date) { return $this->getDateFormated($date); } /** * Format date * * @param Carbon $date * @return string */ private function getDateFormated($date) { return Carbon::parse($date)->format(config('app.locale') == 'fr' ? 'd/m/Y' : 'm/d/Y'); } }
Il y a deux "accessor" qui ont pour objet de formater la date récupérée dans les champs created_at et updated_at. Ce format est défini en fonction de la localisation (on verra cet aspect dans un article ultérieur).
Ainsi on aura pas à se préoccuper du format des dates, elles seront automatiquement bien formées.
Par bestmomo
Nombre de commentaires : 8