Les bases 1/6 : Création du modèle

Publié le 6 juin 2022 par William Suppo
Couverture de l'article 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

  1. Création du modèle
  2. Création du contrôleur
  3. Création des vues
  4. Validation des données
  5. Contrôle d’accès
  6. Les 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 :

Voici le diagramme résultant, créé via l’outil dbdiagram.io :

Untitled

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 :

1php 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 :

1<?php
2 
3use Illuminate\Database\Migrations\Migration;
4use Illuminate\Database\Schema\Blueprint;
5use Illuminate\Support\Facades\Schema;
6 
7return new class extends Migration
8{
9 /**
10 * Run the migrations.
11 *
12 * @return void
13 */
14 public function up()
15 {
16 Schema::create('restaurants', function (Blueprint $table) {
17 $table->id();
18 $table->string('name');
19 $table->string('address');
20 $table->string('type');
21 $table->foreignIdFor(\App\Models\User::class);
22 $table->timestamps();
23 });
24 }
25 
26 /**
27 * Reverse the migrations.
28 *
29 * @return void
30 */
31 public function down()
32 {
33 Schema::dropIfExists('restaurants');
34 }
35};

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 :

1php artisan make:migration --table users AddColumnRoleOnUsersTable

Cela nous crée notre fichier de base que l’on complète comme ceci :

1<?php
2 
3use Illuminate\Database\Migrations\Migration;
4use Illuminate\Database\Schema\Blueprint;
5use Illuminate\Support\Facades\Schema;
6 
7return new class extends Migration
8{
9 /**
10 * Run the migrations.
11 *
12 * @return void
13 */
14 public function up()
15 {
16 Schema::table('users', function (Blueprint $table) {
17 $table->string('role');
18 });
19 }
20 
21 /**
22 * Reverse the migrations.
23 *
24 * @return void
25 */
26 public function down()
27 {
28 Schema::table('users', function (Blueprint $table) {
29 $table->dropColumn('role');
30 });
31 }
32};

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 :

1php 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 :

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Database\Eloquent\Model;
7 
8class Restaurant extends Model
9{
10 use HasFactory;
11 
12 protected $fillable = [
13 'name', 'address', 'type',
14 ];
15 
16 public function user()
17 {
18 return $this->belongsTo(User::class);
19 }
20}

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 :

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Contracts\Auth\MustVerifyEmail;
6use Illuminate\Database\Eloquent\Factories\HasFactory;
7use Illuminate\Foundation\Auth\User as Authenticatable;
8use Illuminate\Notifications\Notifiable;
9 
10class User extends Authenticatable
11{
12 use HasFactory, Notifiable;
13 
14 /**
15 * The attributes that are mass assignable.
16 *
17 * @var array
18 */
19 protected $fillable = [
20 'name',
21 'email',
22 'password',
23 'role'
24 ];
25 
26 /**
27 * The attributes that should be hidden for arrays.
28 *
29 * @var array
30 */
31 protected $hidden = [
32 'password',
33 'remember_token',
34 ];
35 
36 /**
37 * The attributes that should be cast to native types.
38 *
39 * @var array
40 */
41 protected $casts = [
42 'email_verified_at' => 'datetime',
43 ];
44 
45 public function restaurants()
46 {
47 return $this->hasMany(Restaurant::class);
48 }
49}

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 :

Voici la commande qui nous permet de créer la factory de notre modèle Restaurant :

1php artisan make:factory --model Restaurant RestaurantFactory

Suivi de son contenu pour illustrer le propos :

1<?php
2 
3namespace Database\Factories;
4 
5use Illuminate\Database\Eloquent\Factories\Factory;
6 
7/**
8 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Restaurant>
9 */
10class RestaurantFactory extends Factory
11{
12 /**
13 * Define the model's default state.
14 *
15 * @return array<string, mixed>
16 */
17 public function definition()
18 {
19 return [
20 'name' => $this->faker->randomElement([
21 'Au fin gourmet', "A l'ombre du Sakura", "Slow food", "Pizza Yolo",
22 ]),
23 'address' => $this->faker->address,
24 'type' => $this->faker->randomElement(['Asiatique', 'Tradi', 'Italien'])
25 ];
26 }
27}

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 :

1<?php
2 
3namespace Database\Factories;
4 
5use App\Models\User;
6use Illuminate\Database\Eloquent\Factories\Factory;
7use Illuminate\Support\Str;
8 
9/**
10 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
11 */
12class UserFactory extends Factory
13{
14 /**
15 * The name of the factory's corresponding model.
16 *
17 * @var string
18 */
19 protected $model = User::class;
20 
21 /**
22 * Define the model's default state.
23 *
24 * @return array<string, mixed>
25 */
26 public function definition()
27 {
28 return [
29 'name' => $this->faker->name(),
30 'email' => $this->faker->unique()->safeEmail(),
31 'email_verified_at' => now(),
32 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
33 'remember_token' => Str::random(10),
34 'role' => 'user',
35 ];
36 }
37 
38 /**
39 * Indicate that the model's email address should be unverified.
40 *
41 * @return static
42 */
43 public function unverified()
44 {
45 return $this->state(function (array $attributes) {
46 return [
47 'email_verified_at' => null,
48 ];
49 });
50 }
51 
52 public function admin()
53 {
54 return $this->state(function (array $attributes) {
55 return [
56 'role' => 'admin',
57 ];
58 });
59 }
60}

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 :

Nous allons placer ces instructions dans la classe Database\Seeders\DatabaseSeeder ainsi :

1<?php
2 
3namespace Database\Seeders;
4 
5use App\Models\Restaurant;
6use App\Models\User;
7use Illuminate\Database\Console\Seeds\WithoutModelEvents;
8use Illuminate\Database\Seeder;
9 
10class DatabaseSeeder extends Seeder
11{
12 /**
13 * Seed the application's database.
14 *
15 * @return void
16 */
17 public function run()
18 {
19 User::factory()->count(1)->admin()->create([
20 'email' => 'admin@example.net',
21 ]);
22 
23 User::factory()->count(5)
24 ->has(Restaurant::factory()->count(2))
25 ->create();
26 }
27}

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 !

Source : https://github.com/laravel-fr/support-les-bases/tree/v1
William Suppo avatar
William Suppo
Je mange du PHP au p'tit dej', et vous ?

A lire

Autres articles de la même catégorie