Logomark

LARAVEL

Un framework qui rend heureux
Voir cette catégorie
Vers le bas
Voir cette série
Cours Laravel 12 – les données – migrations et modèles
Mercredi 5 mars 2025 14:40

Dans ce chapitre, nous allons explorer les bases de données, un domaine vaste et essentiel pour lequel Laravel offre des solutions efficaces. Nous commencerons par découvrir les migrations et les modèles.

Les migrations

Une migration permet de créer et de mettre à jour le schéma d'une base de données. En d'autres termes, vous pouvez créer des tables, ajouter ou supprimer des colonnes, créer des index, et bien plus encore. Toutes les opérations de maintenance de vos tables peuvent être gérées par cet outil, offrant ainsi un suivi précis de vos modifications.

La configuration de la base

Vous devez dans un premier temps avoir une base de données. Laravel permet de gérer les bases de type MySQL (et MariaDB), PostgreSQL, SQLite et SQL Server. Il est possible d'utiliser MongoDB en installant le package mongodb/laravel-mongodb. Je ferai tous les exemples avec MySQL mais le code sera aussi valable pour les autres types de bases.

Il faut indiquer où se trouve votre base, son nom, le nom de l’utilisateur, le mot de passe dans le fichier de configuration .env :

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

Ici, nous avons les valeurs par défaut à l’installation de Laravel. Il faudra évidemment modifier ces valeurs selon votre contexte de développement et définir surtout le nom de la base, le nom de l'utilisateur et le mot de passe. Pour une installation de MySql en local, en général l'utilisateur est root et on n'a pas de mot de passe :

DB_CONNECTION=mysql 
DB_HOST=127.0.0.1 
DB_PORT=3306 
DB_DATABASE=laravel12 
DB_USERNAME=root 
DB_PASSWORD=

Artisan

Nous avons déjà utilisé Artisan qui permet de faire beaucoup de choses, vous avez un aperçu des commandes en entrant :

php artisan

Vous avez une longue liste. Pour ce chapitre, nous allons nous intéresser uniquement à celles qui concernent les migrations :

Nous disposons de six commandes :

  • fresh : supprime toutes les tables et relance la migration.
  • install : crée et initialise la table de référence des migrations.
  • refresh : réinitialise et relance les migrations.
  • rollback : annule la dernière migration effectuée.
  • status : fournit des informations sur l'état actuel des migrations.

Installation

En examinant le dossier database/migrations, vous trouverez trois migrations déjà présentes :

  • table users : une migration de base pour créer la table des utilisateurs.
  • table cache : une migration dédiée à la gestion du cache.
  • table jobs_table : une migration concernant les files d'attente.

Puisque ces migrations sont présentes, autant les utiliser pour nous entrainer. Commencez par créer une base MySQL et informez .env, par exemple :

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel12
DB_USERNAME=root
DB_PASSWORD=

Lancez alors la commande install

On se retrouve alors avec une table migrations dans la base avec cette structure : Pour le moment cette table est vide, elle va se remplir au fil des migrations pour les garder en mémoire.

Vous n'aurez jamais à intervenir directement sur cette table qui est là juste pour la gestion effectuée par Laravel.

Constitution d'une migration

Si vous ouvrez le fichier database/migrations/0001_01_01_000000_create_users_table.php vous trouvez ce code :

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });

        Schema::create('password_reset_tokens', function (Blueprint $table) {
            $table->string('email')->primary();
            $table->string('token');
            $table->timestamp('created_at')->nullable();
        });

        Schema::create('sessions', function (Blueprint $table) {
            $table->string('id')->primary();
            $table->foreignId('user_id')->nullable()->index();
            $table->string('ip_address', 45)->nullable();
            $table->text('user_agent')->nullable();
            $table->longText('payload');
            $table->integer('last_activity')->index();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('users');
        Schema::dropIfExists('password_reset_tokens');
        Schema::dropIfExists('sessions');
    }
};

Vous remarquez que cette migration n'est pas seulement destinée à créer la table users. On va aussi avoir la création des tables password_reset_tokens et sessions.

On dispose dans cette classe de deux fonctions :

  • up : ici, on a le code de création des tables et de leurs colonnes
  • down : ici, on a le code de suppression des tables

Lancer les migrations

Pour lancer les migrations on utilise la commande migrate :

On voit que les 3 migrations présentes ont été exécutées mais on trouve bien plus de tables dans la base (en plus de celle de gestion des migrations) :

On ne va pas s'inquiéter de ça pour le moment.

Pour comprendre le lien entre migration et création de la table associée voici une illustration pour la table users :

La méthode timestamps permet la création des deux colonnes created_at et updated_at.

Annuler ou rafraichir une migration

Pour annuler une migration on utilise la commande rollback

Les méthodes down des migrations sont exécutées et les tables sont supprimées. Pour annuler les migrations précédentes et relancer en une seule opération on utilise la commande refresh :

 

Pour éviter d'avoir à coder la méthode down on a la commande fresh qui supprime automatiquement les tables concernées :

Créer une migration

Il existe une commande d'artisan pour créer un squelette de migration :

La migration est créée dans le dossier :

Avec ce code de base :

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        //
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        //
    }
};

Il faut ensuite compléter ce code selon vos besoins !

Le nom du fichier de migration commence par sa date de création, ce qui conditionne son positionnement dans la liste. L'élément important à prendre en compte est que l'ordre des migrations prend une grande importance lorsqu'on a des clés étrangères !

La population (seeding)

En plus de faciliter les migrations, Laravel offre également des fonctionnalités de seeding, permettant de remplir facilement les tables de votre base de données avec des enregistrements. Les classes de seeding sont situées dans le répertoire databases/seeders :

Vous êtes libre d'organiser les classes dans ce répertoire comme bon vous semble : vous pouvez conserver une seule classe ou en créer plusieurs pour une meilleure organisation.

Lors de l'installation de Laravel, vous disposez de la classe DatabaseSeeder avec le code suivant :

<?php

namespace Database\Seeders;

use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        // User::factory(10)->create();

        User::factory()->create([
            'name' => 'Test User',
            'email' => 'test@example.com',
        ]);
    }
}

Si vous lancez la population avec cette commande :

php artisan db:seed

On vérifie dans la table qu'on a bien eu la création de l'utilisateur :

Je parlerai plus tard des factories.

Eloquent

Laravel propose un ORM (Object-Relational Mapping, ou mappage objet-relationnel en français) très performant.

De quoi s'agit-il ?

Il s'agit de représenter tous les éléments de la base de données sous forme d'objets manipulables.

Quel est l'intérêt ?

Cela simplifie grandement les opérations sur la base de données, comme nous le verrons dans cette partie du cours.

Avec Eloquent, chaque table est représentée par une classe qui étend la classe Model. Vous pouvez créer un modèle en utilisant Artisan :

php artisan make:model Test

 

On trouve le fichier ici :

Avec cette trame de base :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Test extends Model
{
    //
}

On peut créer un modèle en même temps que la migration pour la table avec cette syntaxe :

php artisan make:model Test -m

Un exemple

Nous allons encore prendre un exemple simple de gestion de contacts mais cette fois en allant plus loin avec Eloquent.

Le contrôleur

On crée un contrôleur avec cette commande :

php artisan make:controller ContactsController

 

On ajoute ces deux méthodes :

  • create : pour appeler la vue avec le formulaire de création
  • store : pour valider la saisie et enregistrer le contact dans la base
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\View\View;

class ContactsController extends Controller
{
    public function create(): View
    {

    }
 
    public function store(Request $request)
    {

    }
}

Il faut à présent coder ces deux méthodes...

Les routes

On a besoin de deux routes :

  • une route GET appeler la méthode create du contrôleur
  • une route POST appeler la méthode store du contrôleur
use App\Http\Controllers\ContactsController;

Route::get('contact', [ContactsController::class, 'create'])->name('contact.create');
Route::post('contact', [ContactsController::class, 'store'])->name('contact.store');

On nomme les routes pour pouvoir les utiliser plus facilement et c'est une bonne habitude à prendre.

Le modèle et la migration

On crée le modèle et la migration tant qu'on y est pour la table contacts :

php artisan make:model Contact -m


Dans le code généré pour la migration on ajoute deux colonnes (message et email) :

public function up(): void
{
    Schema::create('contacts', function (Blueprint $table) {
        $table->id();
        $table->text('message');
        $table->string('email');
        $table->timestamps();
    });
}

Et on lance la migration pour la table contacts :

Par convention le modèle Contact sait qu'il est relié à la table contacts. Si on sortait de la convention de nommage il faudrait ajouter une propriété au modèle pour spécifier le nom de la table.

Le formulaire

On va continuer à utiliser le template déjà vu dans les précédents articles (resources/views/template.blade.php) :

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Mon joli site</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
        <style> 
            textarea { resize: none; }
            .card { width: 25em; }
        </style>
    </head>
    <body>
        @yield('contenu')
    </body>
</html>

On crée la vue du formulaire (contact.blade.php) :

@extends('template')

@section('contenu')
    <br>
    <div class="container">
        <div class="row card text-white bg-dark">
            <h4 class="card-header">Contactez-moi</h4>
            <div class="card-body">
                <form action="{{ route('contact.create') }}" method="POST">
                    @csrf
                    <div class="mb-3">
                        <input type="email" class="form-control  @error('email') is-invalid @enderror" name="email" id="email" placeholder="Votre email" value="{{ old('email') }}">
                        @error('email')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    <div class="mb-3">
                        <textarea class="form-control  @error('message') is-invalid @enderror" name="message" id="message" placeholder="Votre message">{{ old('message') }}</textarea>
                        @error('message')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    <button type="submit" class="btn btn-secondary">Envoyer !</button>
                </form>
            </div>
        </div>
    </div>
@endsection

Rien de nouveau ici, puisque c'est pratiquement le même code qu'on avait déjà vu. Remarquez l'attribut action du formulaire dans lequel on utilise le nom de la route ainsi que l'helper route :

<form action="{{ route('contact.create') }}" method="POST">

Il ne reste plus qu'à coder le contrôleur pour envoyer la vue :

public function create(): View
{
    return view('contact');
}

Et on devrait l'obtenir à l'adresse .../contact :

L'enregistrement

Maintenant on doit coder le traitement de la soumission du formulaire dans le contrôleur :

use App\Models\Contact;

class ContactsController extends Controller
{
    ...
 
    public function store(Request $request)
    {
        $request->validate([
            'email' => 'bail|required|email',
            'message' => 'bail|required|max:500'
        ]);
    
        $contact = new Contact;
        $contact->email = $request->email;
        $contact->message = $request->message;
        $contact->save();
    
        return "C'est bien enregistré !";
    }
}

Je ne reviens pas sur la validation qu'on a vu dans un précédent article.

On crée une nouvelle instance du modèle, on renseigne ses propriétés et on finit avec un save pour l'enregistrer dans la base. Et si tout se passe bien on doit trouver l'enregistrement dans la table :

Méthode create et assignement de masse

On peut aussi utiliser la méthode create du modèle pour enregistrer dans la base :

Contact::create($request->all());

Ce code remplace dans la méthode store celui-ci :

$contact = new Contact;
$contact->email = $request->email;
$contact->message = $request->message;
$contact->save();

La méthode create d'Eloquent attend un tableau de données. Si vous utilisez ce code vous allez tomber sur cette erreur :

Par sécurité ce type d’assignement de masse (on transmet directement un tableau de valeurs issues du client avec la méthode create) est limité  par une propriété au niveau du modèle qui désigne précisément les noms des colonnes susceptibles d’être modifiées. Dans le modèle Contact on ajoute ce code :

class Contact extends Model
{
    ...
    protected $fillable = ['email', 'message'];
}

Ce sont les seules colonnes qui seront impactées par la méthode create (et équivalentes). Attention à cela lorsque vous avez un bug mystérieux avec des colonnes qui ne se mettent pas à jour !

Mais quel est le risque ?

Imaginez qu'il y ait une autre colonne avec des données sensibles et non prévue dans le formulaire, mais qu'un petit malin l'ajoute à la requête, cette colonne serait mise à jour en même temps que les autres !

Le modèle en détail

Modifiez ainsi le code dans la méthode store du contrôleur ContactController :

dd(Contact::create ($request->all ()));

L'helper dd est bien pratique (il existe aussi l'helper ddd) il regroupe un var_dump et un die. Ici si on soumet le formulaire on va observer de plus près le modèle créé parce que la méthode create renvoie ce modèle 

App\Models\Contact {#452 ▼ // app\Http\Controllers\ContactsController.php:23
  #connection: "mysql"
  #table: "contacts"
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  +preventsLazyLoading: false
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: true
  #escapeWhenCastingToString: false
  #attributes: array:5 [▼
    "email" => "toto@ici.fr"
    "message" => "Mon petit message"
    "updated_at" => "2025-03-05 13:26:37"
    "created_at" => "2025-03-05 13:26:37"
    "id" => 3
  ]
  #original: array:5 [▶]
  #changes: []
  #casts: []
  #classCastCache: []
  #attributeCastCache: []
  #dateFormat: null
  #appends: []
  #dispatchesEvents: []
  #observables: []
  #relations: []
  #touches: []
  +timestamps: true
  +usesUniqueIds: false
  #hidden: []
  #visible: []
  #fillable: array:2 [▶]
  #guarded: array:1 [▶]
}

On a pas mal de propriétés mais en particulier des attributs (attributes) et on se rend compte que chacun de ces attributs correspond à une colonne de la table contact.

En résumé

  • La base de données doit être configurée pour fonctionner avec Laravel.
  • Les migrations permettent de modifier le schéma des tables de la base de données.
  • Eloquent offre une représentation des tables sous forme d'objets, simplifiant ainsi la manipulation des enregistrements.
  • L'assignement massif est restreint par la propriété $fillable pour des raisons de sécurité.


Par bestmomo

Aucun commentaire

Article précédent : Cours Laravel 12 – les bases – injection de dépendance, conteneur et façades
Article suivant : Cours Laravel 12 – les données – les ressources (1/2)
Cet article contient un quiz. Vous devez être authentifié pour y avoir acces.