Dans ce chapitre nous allons commencer à aborder les bases de données. C’est un vaste sujet auquel Laravel apporte des réponses efficaces. Nous allons commencer par voir les migrations et les modèles.
Les migrations
Une migration permet de créer et de mettre à jour un schéma de base de données. Autrement dit, vous pouvez créer des tables, des colonnes dans ces tables, en supprimer, créer des index… Tout ce qui concerne la maintenance de vos tables peut être pris en charge par cet outil. Vous avez ainsi un suivi 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. 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=laravel11
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 :
On dispose de 6 commandes :
- fresh : supprime toutes les tables et relance la migration (commande apparue avec la version 5.5)
- install : crée et informe la table de référence des migrations
- refresh : réinitialise et relance les migrations
- rollback : annule la dernière migration
- status : donne des informations sur les migrations
Installation
Si vous regardez dans le dossier database/migrations il y a déjà 3 migrations présentes :- table users : c’est une migration de base pour créer une table des utilisateurs,
- table cache : pour le cache,
- table jobs_table : une migration qui concerne les queues,
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel11
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');
}
};
On dispose dans cette classe de deux fonctions :
- up : ici on a le code de création de la table et de ses colonnes
- down : ici on a le code de suppression de la table
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 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 proposer des migrations, Laravel permet aussi la population (seeding), c'est à dire un moyen simple de remplir les tables d'enregistrements. Les classes de la population se trouvent dans le dossier databases/seeders:
On est libre d'organiser les classes comme on veut dans ce dossier : en garder une seule ou en faire plusieurs pour être mieux organisé.
Lorsqu'on installe Laravel on dispose de la classe DatabaseSeeder avec ce code :<?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 (acronyme de object-relational mapping ou en bon Français un mappage objet-relationnel) très performant.
De quoi s’agit-il ?
Tout simplement que tous les éléments de la base de données ont une représentation sous forme d’objets manipulables.
Quel intérêt ?
Tout simplement de simplifier grandement les opérations sur la base comme nous allons le voir dans toute cette partie du cours.
Avec Eloquent une table est représentée par une classe qui étend la classe Model. On peut créer un modèle avec Artisan :
php artisan make:model Test
On trouve le fichier ici :
Avec cette trame de base :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Test extends Model
{
use HasFactory;
}
Je parlerai plus tard des factories et donc de ce trait présent par défaut dans les modèles.
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 va créer un contrôleur avec cette commande :php artisan make:controller ContactsController
On va créer 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 va falloir coder ces méthodes...
Les routes
Il nous faut 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.
Le modèle et la migration
On va créer 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 va ajouter 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 :{
$request->validate([
'email' => 'bail|required|email',
'message' => 'bail|required|max:500'
]);
$contact = new \App\Models\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 :\App\Models\Contact::create([
'email' => $request->email,
'message' => $request->message,
]);
Ce code remplace dans la méthode store celui-ci :
$contact = new \App\Models\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 va ajouter 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(\App\Models\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 {#311 ▼ // app\Http\Controllers\ContactsController.php:22
#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" => "2024-03-15 17:54:39"
"created_at" => "2024-03-15 17:54:39"
"id" => 2
]
#original: array:5 [▶]
#changes: []
#casts: []
#classCastCache: []
#attributeCastCache: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#relations: []
#touches: []
+timestamps: true
#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 d’intervenir sur le schéma des tables de la base.
- Eloquent permet une représentation des tables sous forme d’objets pour simplifier les manipulations des enregistrements.
- L'assignement de masse est limité par la propriété $fillable pour des raisons de sécurité.
Par bestmomo
Aucun commentaire