Laravel

Un framework qui rend heureux

Voir cette catégorie
Vers le bas
Cours Laravel 5.3 – les données - migrations et modèles
Samedi 26 novembre 2016 13:41

À 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 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. 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=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

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 :

  migrate              Run the database migrations
...                                        
 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 the status of each migration

Créer la migration

On va créer la migration pour notre table :

php artisan make:migration create_emails_table

Si vous regardez maintenant dans le dossier database/migrations vous trouvez un fichier du genre 2016_08_31_212841_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 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 :

On garde que la nouvelle migration

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

<?php

use Illuminate\Support\Facades\Schema;
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é,

  • un champ "email" de type texte,

  • des champs pour mémoriser les dates de création et de modification.

Voilà le code correspondant :

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

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,

  • deux colonnes pour les dates (générées par la méthode timestamps).

Pour la méthode down on va juste supprimer la table avec un drop :

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

Notre migration est maintenant créée.

Utiliser la migration

On va maintenant utiliser la migration (méthode up de la migration) :

On crée la table avec la migration

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

La table

Vous avez aussi la création d'une table migrations :

La table

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

Rollback de la table

La table a maintenant été supprimée de la base. Comme on va avoir besoin de cette table on relance la migration.

Vous disposez également de la commande status pour savoir où vous en êtes :

La commande

Mais pour le moment avec une seule migration il n'y a vraiment pas de quoi se perdre .

Eloquent et le modèle

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 se contenter de ce code pour notre exemple.

La validation

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

php artisan make:request EmailRequest

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 Illuminate\Foundation\Http\FormRequest;

class EmailRequest extends FormRequest
{
    /**
     * 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' => 'bail|required|email|unique:emails'];
    }
}

On a 4 règles :

  • bail : on s'arrête à la première erreur,

  • 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 table emails (on sous-entend qu'il s'agit de la colonne email).

Les routes et le contrôleur

Routes

On va avoir deux routes :

Route::get('email', 'EmailController@create'); 
Route::post('email', 'EmailController@store')->name('store.email');

L'url de base sera : http://monsite.fr/email

Contrôleur

On crée le contrôleur avec Artisan :

php artisan make:controller EmailController

On le trouve dans son dossier :

Le nouveau contrôleur pour les emails

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

<?php

namespace App\Http\Controllers;

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

class EmailController extends Controller
{
    public function create()
    {
        return view('email');
    }

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

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

$email = new Email;
$email->email = $request->email;
$email->save();

Ici on crée une nouvelle instance de Email. 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 (resources/views/template.blade.php)  :

<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Les emails</title>
    {!! Html::style('<a href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css</a>') !!}
    {!! Html::style('<a href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css">https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css</a>') !!}
    <!--[if lt IE 9]>
      {{ Html::style('<a href="https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js">https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js</a>') }}
      {{ Html::style('<a href="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js">https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js</a>') }}
    <![endif]-->
  </head>
  <body>
    @yield('contenu')
  </body>
</html>

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' => 'store.email']) !!}
                    <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

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

Le fonctionnement

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

Soumission d'une adresse

Je reçois la confirmation :

La confirmation

Je regarde dans la base :

Je soumets la même adresse :

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

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 :

$email = new Email;
$email->email = $request->email;
$email->save();

Autrement dit nous avons lié de façon étroite le contrôleur et le modèle. Supposons que nous faisions des modifications dans notre base de données et que nous placions l'email dans une autre table. Nous devrions é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 bien 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 create()
    {
        return view('email');
    }

    public function store(EmailRequest $request, Email $email)
    {
        $email->email = $request->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 create()
    {
        return view('email');
    }

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

Maintenant j'injecte une classe de gestion qui possède la méthode save. 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 fichier app/Http/Providers/AppServiceProvider.php :

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

Et le contrôleur :

<?php

namespace App\Http\Controllers;

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

class EmailController extends Controller
{
    public function create()
    {
        return view('email');
    }

    public function store(EmailRequest $request, EmailRepositoryInterface $emailRepository)
    {
        $emailRepository->save($request->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.



Par bestmomo

Nombre de commentaires : 8