• 15 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 11/05/2020

Migrations et modèles

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

À partir de ce chapitre, il serait souhaitable que vous installiez une barre de débogage. La plus utile est celle proposée par barryvdh. Suivez les indications fournies pour l'installation, ça vous fera un bon exercice :).

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.

Pour ce chapitre je vais encore prendre un exemple simple en imaginant un formulaire destiné à l'inscription à une lettre d'information. On va se contenter d'envoyer un email et de mémoriser cet email dans une table d'une base de données.

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.

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 tout 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=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

Ici nous avons les valeurs par défaut à l'installation de Laravel.

Voici par exemple mes réglages  pour ma base de test MySQL nommée "tuto" avec MySQL en local non sécurisé :

DB_DATABASE=tuto
DB_USERNAME=root
DB_PASSWORD=

Artisan

Laravel a un outil en ligne de commande : artisan. Nous avons déjà utilisé cet outil qui permet de faire beaucoup de choses, vous avez un aperçu des commande en entrant :

php artisan

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

make
 ...
 make:migration     Create a new migration file
 ...
migrate
 migrate:install    Create the migration repository
 migrate:refresh    Reset and re-run all migrations
 migrate:reset      Rollback all database migrations
 migrate:rollback   Rollback the last database migration
 migrate:status     Show a the status of each migration

Installer la migration

On va commencer par installer la migration :

php artisan migrate:install
Migration table created successfully.

Si vous regardez l'effet dans votre base vous allez voir qu'une table a été créée :

La table des migrations
La table des migrations

C'est dans cette table que seront mémorisées toutes vos actions au niveau du schéma de la base.

Créer la migration

La deuxième étape consiste à créer la migration pour notre table :

php artisan make:migration create_emails_table
Created Migration: 2015_01_15_121123_create_emails_table

Si vous regardez maintenant dans le dossierdatabase/migrations vous trouvez un fichier du genre 2015_12_27_210631_create_emails_table.php (la partie numérique qui inclut la date sera évidemment différente pour vous) :

La migration créée

Mais il y a déjà des migrations présentes, à quoi servent-elles ?

Il y a déjà effectivement 2 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é.

Nous nous intéresserons à ces migrations lorsque nous verrons l'authentification dans un chapitre ultérieur. Comme nous n'allons pas avoir besoin immédiatement de ces migrations le mieux est de les supprimer pour le moment pour éviter de créer des tables inutiles.

Voici le contenu de la migration que nous venons de créer :

<?php

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

class CreateEmailsTable extends Migration {

    /**
	 * Run the migrations.
	 *
	 * @return void
	 */
	public function up()
	{
		//
	}

	/**
	 * Reverse the migrations.
	 *
	 * @return void
	 */
	public function down()
	{
		//
	}

}

On dispose dans cette classe de deux fonctions : 

  • up : ici on mettra le code de création

  • down : ici on mettra le code de suppression

On veut créer une table "emails" avec un id auto-incrémenté et un champ "email" de type texte, et de longueur 100. Voilà le code correspondant :

<?php
public function up()
{
    Schema::create('emails', function(Blueprint $table) {
		$table->increments('id');
		$table->string('email', 100);
	});
}

On demande au constructeur de schéma (Schema) de créer (create) la table "emails". Dans la fonction anonyme on définit ce qu'on veut pour la table :

  • une colonne "id" auto-incrémentée qui sera ainsi la clé primaire de la table,

  • une colonne "email" de type string et de longueur 100.

Pour la méthodedown on va juste supprimer la table avec undrop :

<?php
public function down()
{
    Schema::drop('emails');
}

Notre migration est maintenant créée.

Utiliser la migration

On va maintenant lancer la migration (utilisation de la méthodeup de la migration) :

php artisan migrate
Migrated: 2015_12_27_210631_create_emails_table

Si on regarde maintenant dans la base on trouve la table "emails" avec ces deux colonnes :

La table
La table "emails"

Si vous avez fait une erreur vous pouvez revenir en arrière avec unrollback qui annule la dernière migration effectuée (utilisation de la méthodedown de la migration) :

php artisan migrate:rollback
Rolled back: 2015_12_27_210631_create_emails_table

La table a maintenant été supprimée de la base. Comme on va avoir besoin de cette table on relance la migration. On peut aussi effectuer un rafraîchissement de toutes les migrations avec la commanderefresh (rollback de toutes les migrations et nouveau lancement de toutes les migrations).

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. Pour notre table  emails on va à nouveau utiliser Artisan pour la création du modèle :

php artisan make:model Email

On trouve le fichier ici :

Le nouveau modèle pour les emails

Avec cette trame de base :

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Email extends Model
{
    //
}

On va compléter ainsi le code :

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Email extends Model 
{
    
    protected $table = 'emails';
    
    public $timestamps = false;

}

Vous voyez c'est tout simple ! On renseigne le nom de la table associée au modèle. D'autre part par défaut Eloquent tient à jour des colonnes created_at et updated_at dans la table. Comme nous ne les avons pas prévues, pour désactiver cette action on est obligé de mettre àfalse la propriététimestamps. On va mettre ce modèle directement dans le dossierapp,  il est évident que pour une application réelle on organisera les dossiers pour bien ranger tous nos fichiers mais pour le moment on va faire simple.

Nous allons voir maintenant comment utiliser cette classe en construisant notre petite application.

La validation

Pour la validation on va encore créer une requête de formulaire :

php artisan make:request EmailRequest
Request created successfully.

On trouve la requête dans son dossier :

La requête de formulaire

La voici avec le code complété :

<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class EmailRequest extends Request
{

    /**
	 * Determine if the user is authorized to make this request.
	 *
	 * @return bool
	 */
	public function authorize()
	{
		return true;
	}

	/**
	 * Get the validation rules that apply to the request.
	 *
	 * @return array
	 */
	public function rules()
	{
		return ['email' => 'required|email|unique:emails'];
	}

}

On a 3 règles :

  • required : le champ est requis,

  • email : on doit avoir une adresse email valide,

  • unique : l'email ne doit pas déjà exister (unique) dans la tableemails (on sous-entend qu'il s'agit de la colonneemail).

Remarquez la puissance de la troisième règle : Eloquent va vérifier que notre email n'existe pas déjà dans la table ! 

Les routes

On va avoir deux routes :

<?php
Route::get('email', 'EmailController@getForm');
Route::post('email', ['uses' => 'EmailController@postForm', 'as' => 'storeEmail']);

Remarquez que la seconde route est nommée (storeEmail).

L'url de base sera donc :

http://monsite.fr/email

Le contrôleur

On va créer un contrôleurEmailController :

Le contrôleur EmailController

Le code du contrôleur reprend l'essentiel de ce que nous avons vu dans les chapitres précédents en utilisant à nouveau la validation injectée :

<?php

namespace App\Http\Controllers;

use App\Email;
use App\Http\Requests\EmailRequest;

class EmailController extends Controller
{

    public function getForm()
	{
		return view('email');
	}

	public function postForm(EmailRequest $request)
	{
		$email = new Email;
		$email->email = $request->input('email');
		$email->save();
		
		return view('email_ok');
	}

}

La nouveauté réside uniquement dans l'utilisation du modèle :

<?php
$email = new Email;
$email->email = $request->input('email');
$email->save();

Ici on crée une nouvelle instance deEmail. On affecte l'attribut email avec la valeur de l'entrée. Enfin on demande au modèle d'enregistrer cette ligne effectivement dans la table (save).

Les vues

On va utiliser le même tempate que dans les précédents chapitres. Voici la vue pour le formulaire(resources/views/email.blade.php) :

@extends('template')

@section('contenu')
    <br>
    <div class="col-sm-offset-4 col-sm-4">
		<div class="panel panel-info">
			<div class="panel-heading">Inscription à la lettre d'information</div>
			<div class="panel-body"> 
				{!! Form::open(['route' => 'storeEmail']) !!}
					<div class="form-group {!! $errors->has('email') ? 'has-error' : '' !!}">
						{!! Form::email('email', null, array('class' => 'form-control', 'placeholder' => 'Entrez votre email')) !!}
						{!! $errors->first('email', '<small class="help-block">:message</small>') !!}
					</div>
					{!! Form::submit('Envoyer !', ['class' => 'btn btn-info pull-right']) !!}
				{!! Form::close() !!}
			</div>
		</div>
	</div>
@endsection

Cette vue ne présente aucune nouveauté pour vous si ce n'est l'utilisation du nom de la route, elle répond à l'url (avec le verbe get) :

http://monsite.fr/email

L'aspect est le suivant :

Le formulaire
Le formulaire

Voici maintenant la vue de confirmation (resources/views/email_ok.blade.php) :

@extends('template')

@section('contenu')
    <br>
	<div class="col-sm-offset-3 col-sm-6">
		<div class="panel panel-info">
			<div class="panel-heading">Inscription à la lettre d'information</div>
			<div class="panel-body"> 
				Merci. Votre adresse a bien été prise en compte.
			</div>
		</div>
	</div>
@endsection

Avec cet aspect :

La confirmation
La confirmation

Le fonctionnement

Voyons maintenant si tout se passe bien. Je soumets une adresse :

Soumission d'une adresse
Soumission d'une adresse

Je reçois la confirmation :

La confirmation
La confirmation

Je regarde dans la base :

L'adresse dans la base

Je soumets la même adresse :

Soumission d'une adresse existante

Voyons un peu les requêtes générées par Eloquent avec par exemple la soumission de l'adresse toto@gui.com (vous les trouvez à la rubrique Queries de la barre de débogage) : 

select count(*) as aggregate from `emails` where `email` = 'toto@gui.com'
insert into `emails` (`email`) values ('toto@gui.com')

La première requête est destinée à tester la présence éventuelle de l'adresse dans la table pour répondre à la règle "unique". La seconde insère l'enregistrement dans la table. Vous voyez qu'Eloquent vous simplifie la tâche, vous n'avez pas besoin d'écrire les requêtes SQL, il le fait pour vous. Vous vous contentez de manipuler un objet.

N'hésitez pas à regarder les informations de la barre de débogage, vous y trouverez de précieux renseignements sur les requêtes (HTTP et SQL), les vues utilisées, les routes, les délais, les exceptions générées... Vous avez aussi un historique en cliquant sur la petite image de dossier :

Ouvrir l'historique de la barre
Ouvrir l'historique de la barre

Organisation du code

Maintenant posons-nous à nouveau la question de l'organisation du code. Dans le contrôleur nous avons mis la gestion du modèle :

<?php
$email = new Email;
$email->email = $request->input('email');
$email->save();

Autrement dit nous avons lié de façon étroite le contrôleur et le modèle. Supposons que nous faisons des modifications dans notre base de données et que nous plaçons l'email dans une autre table. Nous devrons évidemment intervenir dans le code du contrôleur pour tenir compte de cette modification.

Vous pouvez évidemment considérer que c'est peu probable, que la modification du code n'est pas très importante... Mais ici on a une application très simple, dans une situation réelle l'utilisation des modèles sont nombreux et alors la question devient plus pertinente.

Première version

Dans ce cours je m'efforce de vous entraîner à prendre de bonnes habitudes. Plutôt que d'instancier directement une classe dans une autre il vaut mieux une injection et laisser faire le conteneur. Regardez cette nouvelle version du contrôleur :

<?php

namespace App\Http\Controllers;

use App\Email;
use App\Http\Requests\EmailRequest;

class EmailController extends Controller
{

    public function getForm()
	{
		return view('email');
	}

	public function postForm(
		EmailRequest $request,
		Email $email)
	{
		$email->email = $request->input('email');
		$email->save();
		
		return view('email_ok');
	}

}

Maintenant le modèle est injecté dans la méthode, c'est plus élégant et efficace. Si jamais vous changez de modèle vous n'avez plus qu'un changement de code limité sur le contrôleur. Mais ce n'est pas encore parfait. 

Seconde version

Dans l'idéal on veut que notre contrôleur ne soit pas du tout concerné par un changement dans la gestion des modèles. Voici une façon de procéder :

<?php
namespace App\Http\Controllers;

use App\Http\Requests\EmailRequest;
use App\Repositories\EmailRepository;

class EmailController extends Controller
{

    public function getForm()
	{
		return view('email');
	}

	public function postForm(
		EmailRequest $request,
		EmailRepository $emailRepository)
	{
		$emailRepository->save($request->input('email'));
		
		return view('email_ok');
	}

}

Maintenant j'injecte une classe de gestion qui possède la méthodesave. Voici le contrat avec une interface (app/Repositories/EmailRepositoryInterface) :

<?php

namespace App\Repositories;

interface EmailRepositoryInterface
{

    public function save($mail);
}

Et voici la classe qui implémente cette interface (app/Repositories/EmailRepository) :

<?php

namespace App\Repositories;

use App\Email;

class EmailRepository implements EmailRepositoryInterface
{

    protected $email;

	public function __construct(Email $email)
	{
		$this->email = $email;
	}

	public function save($mail)
	{
        $this->email->email = $mail;
        $this->email->save();
	}

}

Le modèle est injecté dans cette classe. Je l'ai injecté dans le constructeur pour généraliser la démarche en imaginant qu'on créera d'autres méthodes que l'on peut regrouper ici  pour gérer les enregistrements. Le code est maintenant parfaitement organisé, facile à modifier et à tester.‌

La gestion

Troisième version

On peut enfin, comme on l'a déjà vu, référencer l'interface plutôt que la classe mais dans ce cas il faut informer le conteneur de la dépendance. Modifiez ainsi le fichierapp/Http/Providers/AppServiceProvider.php :

<?php 
public function register()
{
    ...

	$this->app->bind(
		'App\Repositories\EmailRepositoryInterface', 
		'App\Repositories\EmailRepository'
	);
}

Et finalement le contrôleur :

<?php

namespace App\Http\Controllers;

use App\Http\Requests\EmailRequest;
use App\Repositories\EmailRepositoryInterface;

class EmailController extends Controller
{

    public function getForm()
	{
		return view('email');
	}

	public function postForm(
		EmailRequest $request,
		EmailRepositoryInterface $emailRepository)
	{
		$emailRepository->save($request->input('email'));
		
		return view('email_ok');
	}

}

Maintenant, étant donné que le conteneur sait quelle classe instancier à partir de l'interface passée en paramètre, vous avez un code propre et facile à maintenir et à tester. Si vous changez d'avis sur la manière de stocker les emails il vous suffit de décider d'instancier une autre classe à partir de l'interface, tant que le contrat passé avec le contrôleur ne change pas !

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.

  • Il est judicieux de prévoir la gestion du modèle dans une classe injectée dans le contrôleur.

  • La barre de débogage  donne de précieux renseignements sur les requêtes. 

Exemple de certificat de réussite
Exemple de certificat de réussite