Laravel

Un framework qui rend heureux

Voir cette catégorie
Vers le bas
Cours Laravel 9 – les données – migrations et modèles
Lundi 14 février 2022 13:08

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, Postgres, 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=mysql 
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.

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à 4 migrations présentes :
  • table users : c’est une migration de base pour créer une table des utilisateurs,
  • table password_resets : c’est une migration liée à la précédente qui permet de gérer le renouvellement des mots de passe en toute sécurité,
  • table failed_jobs : une migration qui concerne les queues,
  • table personal_access_tokens concerne les api.
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=laravel9
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/2014_10_12_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.
     *
     * @return void
     */
    public function up()
    {
        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();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
};
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 4 migrations présentes ont été exécutées et on trouve les 4 tables dans la base (en plus de celle de gestion des migrations) : 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.
     *
     * @return void
     */
    public function up()
    {
        //
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        //
    }
};
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/seeds:

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 Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

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

On a un appel commenté pour remplir la table users. Décommentez cette ligne et lancez la population avec cette commande :

php artisan db:seed
On vérifie dans la table qu'on a bein eu la création de 10 utilisateurs :

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 :

Pour ceux qui ont travaillé avec les versions anciennes de Laravel on a désormais un dossier Models par défaut.

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
class ContactsController extends Controller
{
    public function create()
    {
        //
    }

    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()
{
    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.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" 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()
{
    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 :
public function store(Request $request)
{
    $this->validate($request, [
        '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 le 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 {#1132 ▼
  #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" => "2022-01-22 17:52:47"
    "created_at" => "2022-01-22 17:52:47"
    "id" => 1
  ]
  #original: array:5 [▶]
  #changes: []
  #casts: []
  #classCastCache: []
  #attributeCastCache: []
  #dates: []
  #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