
Les bases 1/6 : Création du modèle
Contexte
La fonctionnalité que nous allons développer dans cette suite d’articles est la gestion de restaurants.
Un utilisateur authentifié pourra créer ses restaurants, les éditer et les supprimer. Il sera le seul, avec les admins, à pouvoir le faire sur les siens.
Nous allons passer en revue aussi bien l’élaboration du modèle de données, que la validation des entrées du formulaire, les droits, le contrôleur, les vues et en conclusion les tests.
Sommaire
- Création du modèle
- Création du contrôleur
- Validation des données
- Contrôle d’accès
- Création des vues
- Tests
Le diagramme
L’élaboration d’un diagramme, aussi basique soit-il, a plusieurs vertus.
Il permet de se poser et de réfléchir à la manière dont nos données vont être stocké et des liens qui vont exister entre nos tables. Aussi, il permet à une équipe de se documenter et d’avoir une représentation claire de la base, pourvu qu'elle soit à jour !
Dans notre exemple, nous allons partir sur un cas simple :
- Un utilisateur a un rôle en plus des propriétés de base que propose Laravel
- Un utilisateur dispose de 0 ou plus restaurants
- Un restaurant a un nom, une adresse et un type
Voici le diagramme résultant, créé via l’outil dbdiagram.io :
Les migrations
Maintenant qu’on a définit, à travers le diagramme, la représentation de nos tables et de leur relation, nous allons créer les migrations résultantes.
Pour rappel, les migrations dans Laravel permettent de définir une instruction qui va modifier la structure de notre base via la méthode up
et son inverse via la méthode down
.
C’est donc depuis ces classes que nous allons définir la structure de nos tables.
Nous allons commencer par la migration de la table restaurants
dont nous allons créer le fichier via la commande :
php artisan make:migration --create restaurants CreateRestaurantsTable
Cela va nous pré-remplir notre migration, il nous reste plus qu’à compléter avec les champs particuliers de notre table :
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('restaurants', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('address');
$table->string('type');
$table->foreignIdFor(\App\Models\User::class);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('restaurants');
}
};
La grande nouveauté réside dans l’utilisation de la méthode foreignIdFor
qui permet de déclarer une clé étrangère sur un modèle. Laravel va donc résoudre depuis le modèle la clé primaire à utiliser, très pratique !
Pour ce qui est de la table users
, la migration est livrée à l’installation du projet, or on veut lui ajouter le champ role
. Comment faire ? On va partir du postulat qu’elle peut avoir été jouée dans un environnement et que la modifier aurait aucune incidence.
Une migration jouée dans un environnement est marquée en tant que telle et ne peut être rejouée à moins de
refresh
la base. Ce qui est à prohiber en production !
L’occasion de créer une migration sur une table existante, la table users
, via la commande :
php artisan make:migration --table users AddColumnRoleOnUsersTable
Cela nous crée notre fichier de base que l’on complète comme ceci :
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('role');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};
Les modèles
Maintenant que nous avons nos migrations, nous allons concevoir les modèles liés à ces dernières.
A noter que Laravel nous facilite grandement la vie en respectant ces standards comme le nom des tables au pluriel, le modèle au singulier, la clé primaire
id
, etc.
Commençons par la création du modèle Restaurant
via la commande suivante :
php artisan make:model Restaurant
Encore une fois une commande qui nous fait gagner du temps car il nous reste qu'à compléter le corps de notre classe, ce qui donne :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Restaurant extends Model
{
use HasFactory;
protected $fillable = [
'name', 'address', 'type',
];
public function user()
{
return $this->belongsTo(User::class);
}
}
Ici rien de particulier ci ce n’est la relation avec le modèle User
qui est un belongsTo
.
On aurait pu améliorer la gestion du type à travers un Enum
: nouveauté de Laravel 9 !
Pour ce qui est du modèle User
, il existe déjà, on n’a juste à le compléter comme ceci :
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'role'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function restaurants()
{
return $this->hasMany(Restaurant::class);
}
}
Les factories
A quoi sert une Factory
? Elle permet de définir la nature de la valeur d’une propriété d’un modèle dans le cadre d’un test ou en environnement de développement.
C’est à dire, qu’on va y définir des instructions via l’outil Faker, comme :
- Le nom d’un utilisateur doit avoir la forme M. Dupond
- Le contenu de mon article est un Lorem Ipsum de 300 caractères maximum
Voici la commande qui nous permet de créer la factory de notre modèle Restaurant
:
php artisan make:factory --model Restaurant RestaurantFactory
Suivi de son contenu pour illustrer le propos :
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Restaurant>
*/
class RestaurantFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'name' => $this->faker->randomElement([
'Au fin gourmet', "A l'ombre du Sakura", "Slow food", "Pizza Yolo",
]),
'address' => $this->faker->address,
'type' => $this->faker->randomElement(['Asiatique', 'Tradi', 'Italien'])
];
}
}
Il existe de nombreuses méthodes disponibles via Faker et il est même possible de créer ses propres providers !
L’utilisation de données réalistes permet, lors d’une démo par exemple, de rendre concrètes les interfaces et de faciliter la compréhension et le dialogue avec son publique.
Pour la factory du modèle User
, on définit qu'un utilisateur aura le rôle user
par défaut et nous allons utiliser les states afin de pouvoir lui donner le rôle admin
au besoin comme ceci :
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = User::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
'role' => 'user',
];
}
/**
* Indicate that the model's email address should be unverified.
*
* @return static
*/
public function unverified()
{
return $this->state(function (array $attributes) {
return [
'email_verified_at' => null,
];
});
}
public function admin()
{
return $this->state(function (array $attributes) {
return [
'role' => 'admin',
];
});
}
}
Les seeders
On attaque la dernière étape de ce premier chapitre, les Seeders
.
La fonction du seeder est de créer un jeu de données, souvent pour l’environnement de développement ou pour les tests.
Ils se basent sur la définition des factories pour créer ses objets et remplir notre base de données.
Dans notre exemple, on va lui donner l’instruction de créer les données suivantes :
- Un utilisateur avec le rôle admin et l’email admin@example.net
- 5 utilisateurs avec le rôle user
- Pour chacun des utilisateurs, 2 restaurants
Nous allons placer ces instructions dans la classe Database\Seeders\DatabaseSeeder
ainsi :
<?php
namespace Database\Seeders;
use App\Models\Restaurant;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
User::factory()->count(1)->admin()->create([
'email' => 'admin@example.net',
]);
User::factory()->count(5)
->has(Restaurant::factory()->count(2))
->create();
}
}
Les sources de ce tutoriel sont disponibles sur ce dépôt.
Dans le prochain épisode…
Le premier chapitre est maintenant terminé, nous y avons mis en place les briques essentielles à la gestion de données pour notre application et aussi pour ses tests.
Le prochain sera axé sur l’utilisation de ces modèles dans un contrôleur !
A lire
Autres articles de la même catégorie

Tutoriels
Quelques tips pour phpunit #1
Quelques tips pour améliorer vos performances et votre confort d’utilisation de phpunit.

Tutoriels
Les bases 2/6 : Création du contrôleur
Découverte des notions fondamentales de Laravel à travers un cas concret.

Tutoriels
Les failles RCE dans Laravel
Une faille RCE consiste à injecter puis exécuter arbitrairement du code dans une application, voyons comment exploiter l’une d’entre elles dans un Laravel 9 !