• 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

Les ressources (2/2) et les erreurs

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

Dans ce chapitre nous allons poursuivre notre étude de la ressource pour les utilisateurs. Nous avons au chapitre précédent passé en revue la migration, les routes, le contrôleur et la validation. Il nous reste maintenant à voir la gestion des données, les vues, et aussi comment tout cela s'articule pour fonctionner.

Le gestionnaire de données (repository)

Nous avons vu que nous injectons un gestionnaire de données dans le contrôleur en plus des deux classes de validation :

<?php
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}

Ce gestionnaire est chargé de toutes les actions au niveau de la table des utilisateurs. Voici son code (app/Repositories/UserRepository.php) :

<?php
namespace App\Repositories;
use App\User;
class UserRepository
{
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
private function save(User $user, Array $inputs)
{
$user->name = $inputs['name'];
$user->email = $inputs['email'];
$user->admin = isset($inputs['admin']);
$user->save();
}
public function getPaginate($n)
{
return $this->user->paginate($n);
}
public function store(Array $inputs)
{
$user = new $this->user;
$user->password = bcrypt($inputs['password']);
$this->save($user, $inputs);
return $user;
}
public function getById($id)
{
return $this->user->findOrFail($id);
}
public function update($id, Array $inputs)
{
$this->save($this->getById($id), $inputs);
}
public function destroy($id)
{
$this->getById($id)->delete();
}
}

Vous devez vous retrouver donc avec ce fichier dans le dossier  Repositories :

Le fichier pour la gestion‌

Nous allons à présent analyser ses différentes actions.

getPaginate

Cette méthode contient juste une ligne :

<?php
public function getPaginate($n)
{
return $this->user->paginate($n);
}

Elle est appelée depuis la méthode  index du contrôleur :

<?php
public function index()
{
$users = $this->userRepository->getPaginate($this->nbrPerPage);
$links = $users->render();
return view('index', compact('users', 'links'));
}

Cette méthode répond à l'url du type (avec le verbe  get) :

http://monsite.fr/user

On utilise la méthode  paginate du modèle qui permet de prendre seulement une partie des enregistrements pour faire une pagination. Ici j'ai prévu la valeur 4 (stockée dans la propriété  $nbrPerPage), donc 4 enregistrements par page. Si vous regardez les requêtes SQL générées vous trouverez :

select count(*) as aggregate from `users`
select * from `users` limit 4 offset 0

La première requête sert à connaître le nombre total d'enregistrements dans la table users (donnée nécessaire pour calculer la pagination) et la seconde à sélectionner les 4 premiers pour les afficher sur la première page. Évidemment pour la page 2 on aura la requête :

select * from `users` limit 4 offset 4

Toujours 4 enregistrements mais avec un offset de 4 pour prendre les 4 enregistrements suivants. L'url sera alors :

.../user?page=2

Une fois que les enregistrements sont récupérés dans la table on les retourne au contrôleur. Celui-ci les tranforme sous la forme d'un tableau avec comme clé "users".  Ce tableau sera utilisé par la vue comme nous le verrons bientôt.

La pagination est incluse dans la variable  $links pour être aussi envoyée dans la vue.

getById

Cette méthode contient juste une ligne :

<?php
public function getById($id)
{
return $this->user->findOrFail($id);
}

La méthode  findOrFail essaie de récupérer dans la table l'enregistrement dont on transmet l'id. Si elle n'y parvient pas elle génère une erreur d'exécution.

Voici le genre de requête générée :

select * from `users` where `id` = '2' limit 1

Dans le contrôleur nous avons deux méthodes qui utilisentgetById.

show

Voici la méthode show du contrôleur :

<?php
public function show($id)
{
$user = $this->userRepository->getById($id);
return view('show', compact('user'));
}

Cette méthode répond à l'url (avec le verbe get) :

http://monsite.fr/user/n

Où n est l'id de l'utilisateur qu'on veut afficher.

Une fois que l'enregistrement est ainsi récupéré dans la table on le retourne sous la forme d'un tableau avec comme clé "user". Ce tableau sera utilisé par la vue, comme nous le verrons bientôt.

edit

La méthode  getById est aussi utilisée par la méthode  edit du contrôleur. Cette méthode répond à l'url (avec le verbe get) :

http://monsite.fr/user/n/edit

Où n est l'id de l'utilisateur qu'on veut modifier.

Elle contient juste deux lignes :

<?php
public function edit($id)
{
$user = $this->userRepository->getById($id);
return view('edit', compact('user'));
}

Le principe est exactement le même que pour la méthode  show vue ci-dessus.

update

Cette méthode est destinée à mettre à jour l'enregistrement dans la table à partir des données transmises comme paramètres :

<?php
public function update($id, Array $inputs)
{
$this->save($this->getById($id), $inputs);
}

On récupère l'enregistrement avec la méthode  getById avec l'id transmis. Ensuite on met à jour dans la table avec la méthode privée  save :

<?php
private function save(User $user, Array $inputs)
{
$user->name = $inputs['name'];
$user->email = $inputs['email'];
$user->admin = isset($inputs['admin']);
$user->save();
}

La méthode  update du repository est appelée depuis la méthodeupdate du contrôleur :

<?php
public function update(UserUpdateRequest $request, $id)
{
$this->userRepository->update($id, $request->all());
return redirect('user')->withOk("L'utilisateur " . $request->input('name') . " a été modifié.");
}

Cette méthode répond à l'url (avec le verbeput) :

http://monsite.fr/user/n

Où n est l'id de l'utilisateur qu'on veut modifier.

store

Voici le code de cette méthode :

<?php
public function store(Array $inputs)
{
$user = new $this->user;
$user->password = bcrypt($inputs['password']);
$this->save($user, $inputs);
return $user;
}

Par sécurité le mot de passe entré est ici codé avec l'helper  bcrypt. Ainsi il ne sera pas inscrit en clair dans la table mais sous forme codée.

On crée un nouvel objet User. On renseigne l'attribut  password et on enregistre dans la table avec la méthode privée  save que nous avons déjà vue ci-dessus :

<?php
private function save(User $user, Array $inputs)
{
$user->name = $inputs['name'];
$user->email = $inputs['email'];
$user->admin = isset($inputs['admin']);
$user->save();
}

La méthode du repository est appelée depuis la méthodestore du contrôleur :

<?php
public function store(UserCreateRequest $request)
{
$user = $this->userRepository->store($request->all());
return redirect('user')->withOk("L'utilisateur " . $user->name . " a été créé.");
}

Cette méthode répond à l'url (avec le verbepost) :

http://monsite.fr/user

destroy

Elle contient juste une ligne :

<?php
public function destroy($id)
{
$this->getById($id)->delete();
}

Elle est appelée depuis la méthodedestroy du contrôleur :

<?php
public function destroy($id)
{
$this->userRepository->destroy($id);
return redirect()->back();
}

On supprime un enregistrement avec la méthodedelete du modèle. Remarquez la redirection dans le contrôleur avec la méthodeback. On renvoie ainsi la dernière requête.

Cette méthode répond à l'url (avec le verbedelete) :

http://monsite.fr/user/n

Où n est l'id de l'enregistrement qu'on veut supprimer.

Les vues

Voyons à présent les vues pour l'interaction avec le client. J'ai adopté pour les vues le même nom que les méthodes appelantes du contrôleur pour faciliter la compréhension.

Le template

Nous aurons le même template pour toutes les vues, c'est celui que nous avons déjà utilisé dans les précédents chapitres :

<!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>Mon joli site</title>
{!! Html::style('https://netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css') !!}
{!! Html::style('https://netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css') !!}
<!--[if lt IE 9]>
{{ Html::style('https://oss.maxcdn.com/libs/html5shiv/3.7.2/html5shiv.js') }}
{{ Html::style('https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js') }}
<![endif]-->
<style> textarea { resize: none; } </style>
</head>
<body>
@yield('contenu')
</body>
</html>

Vue index

Cette vue est destinée à afficher la liste paginée des utilisateurs avec des boutons pour pouvoir accomplir toutes les actions. Voici le code de cette vue :

@extends('template')
@section('contenu')
<br>
<div class="col-sm-offset-4 col-sm-4">
@if(session()->has('ok'))
<div class="alert alert-success alert-dismissible">{!! session('ok') !!}</div>
@endif
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">Liste des utilisateurs</h3>
</div>
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Nom</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach ($users as $user)
<tr>
<td>{!! $user->id !!}</td>
<td class="text-primary"><strong>{!! $user->name !!}</strong></td>
<td>{!! link_to_route('user.show', 'Voir', [$user->id], ['class' => 'btn btn-success btn-block']) !!}</td>
<td>{!! link_to_route('user.edit', 'Modifier', [$user->id], ['class' => 'btn btn-warning btn-block']) !!}</td>
<td>
{!! Form::open(['method' => 'DELETE', 'route' => ['user.destroy', $user->id]]) !!}
{!! Form::submit('Supprimer', ['class' => 'btn btn-danger btn-block', 'onclick' => 'return confirm(\'Vraiment supprimer cet utilisateur ?\')']) !!}
{!! Form::close() !!}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{!! link_to_route('user.create', 'Ajouter un utilisateur', [], ['class' => 'btn btn-info pull-right']) !!}
{!! $links !!}
</div>
@endsection

Avec cet aspect :

La vue index
La vue index

Remarquez que pour générer la méthode  delete on est obligé de créer un formulaire. 

La vue teste aussi la présence d'une variable "ok" dans la session et affiche le message correspondant dans une barre si c'est le cas.

Vue show

La vue  show sert à afficher la fiche d'un utilisateur avec son nom, son adresse email et son appartenance éventuelle au groupe des administrateurs :

@extends('template')
@section('contenu')
<div class="col-sm-offset-4 col-sm-4">
<br>
<div class="panel panel-primary">
<div class="panel-heading">Fiche d'utilisateur</div>
<div class="panel-body">
<p>Nom : {{ $user->name }}</p>
<p>Email : {{ $user->email }}</p>
@if($user->admin == 1)
Administrateur
@endif
</div>
</div>
<a href="javascript:history.back()" class="btn btn-primary">
<span class="glyphicon glyphicon-circle-arrow-left"></span> Retour
</a>
</div>
@endsection

Avec cet aspect :

La vue show
La vue show

Vue edit

La vueedit sert à la modification d'un utilisateur, elle affiche un formulaire :

@extends('template')
@section('contenu')
<div class="col-sm-offset-4 col-sm-4">
<br>
<div class="panel panel-primary">
<div class="panel-heading">Modification d'un utilisateur</div>
<div class="panel-body">
<div class="col-sm-12">
{!! Form::model($user, ['route' => ['user.update', $user->id], 'method' => 'put', 'class' => 'form-horizontal panel']) !!}
<div class="form-group {!! $errors->has('name') ? 'has-error' : '' !!}">
{!! Form::text('name', null, ['class' => 'form-control', 'placeholder' => 'Nom']) !!}
{!! $errors->first('name', '<small class="help-block">:message</small>') !!}
</div>
<div class="form-group {!! $errors->has('email') ? 'has-error' : '' !!}">
{!! Form::email('email', null, ['class' => 'form-control', 'placeholder' => 'Email']) !!}
{!! $errors->first('email', '<small class="help-block">:message</small>') !!}
</div>
<div class="form-group">
<div class="checkbox">
<label>
{!! Form::checkbox('admin', 1, null) !!}Administrateur
</label>
</div>
</div>
{!! Form::submit('Envoyer', ['class' => 'btn btn-primary pull-right']) !!}
{!! Form::close() !!}
</div>
</div>
</div>
<a href="javascript:history.back()" class="btn btn-primary">
<span class="glyphicon glyphicon-circle-arrow-left"></span> Retour
</a>
</div>
@endsection

Avec cet aspect :

La vue edit

Vue create

Cette vue sert à afficher le formulaire pour créer un utilisateur, c'est quasiment la même que pour la modification avec le mot de passe en plus :

@extends('template')
@section('contenu')
<div class="col-sm-offset-4 col-sm-4">
<br>
<div class="panel panel-primary">
<div class="panel-heading">Création d'un utilisateur</div>
<div class="panel-body">
<div class="col-sm-12">
{!! Form::open(['route' => 'user.store', 'class' => 'form-horizontal panel']) !!}
<div class="form-group {!! $errors->has('name') ? 'has-error' : '' !!}">
{!! Form::text('name', null, ['class' => 'form-control', 'placeholder' => 'Nom']) !!}
{!! $errors->first('name', '<small class="help-block">:message</small>') !!}
</div>
<div class="form-group {!! $errors->has('email') ? 'has-error' : '' !!}">
{!! Form::email('email', null, ['class' => 'form-control', 'placeholder' => 'Email']) !!}
{!! $errors->first('email', '<small class="help-block">:message</small>') !!}
</div>
<div class="form-group {!! $errors->has('password') ? 'has-error' : '' !!}">
{!! Form::password('password', ['class' => 'form-control', 'placeholder' => 'Mot de passe']) !!}
{!! $errors->first('password', '<small class="help-block">:message</small>') !!}
</div>
<div class="form-group">
{!! Form::password('password_confirmation', ['class' => 'form-control', 'placeholder' => 'Confirmation mot de passe']) !!}
</div>
<div class="form-group">
<div class="checkbox">
<label>
{!! Form::checkbox('admin', 1, null) !!} Administrateur
</label>
</div>
</div>
{!! Form::submit('Envoyer', ['class' => 'btn btn-primary pull-right']) !!}
{!! Form::close() !!}
</div>
</div>
</div>
<a href="javascript:history.back()" class="btn btn-primary">
<span class="glyphicon glyphicon-circle-arrow-left"></span> Retour
</a>
</div>
@endsection

Avec cet aspect :

La vue create

Avec évidemment la validation active :

La validation en action

Nous avons vu dans un chapitre précédent comment avoir ces messages en Français.

Vous devez donc avoir ces 5 vues :

Les cinq vues pour la ressource

Réflexion sur le code

Un repository de base

Notre code fonctionne correctement, mais est-il vraiment performant ? Lorsqu'on crée une classe, une bonne question est de se demander si le code est réutilisable. Si on observe le repository créé pour les utilisateurs on se rend compte qu'il est très ciblé sur le modèle concerné et si on a une autre ressource dans l'application il est fort probable qu'on va bidouiller avec du copier-coller, ce qui est toujours source de répétition de code et d'erreurs. Est-il possible de créer un repository de base pour les ressources ? Voici une solution :

<?php
namespace App\Repositories;
abstract class ResourceRepository
{
protected $model;
public function getPaginate($n)
{
return $this->model->paginate($n);
}
public function store(Array $inputs)
{
return $this->model->create($inputs);
}
public function getById($id)
{
return $this->model->findOrFail($id);
}
public function update($id, Array $inputs)
{
$this->getById($id)->update($inputs);
}
public function destroy($id)
{
$this->getById($id)->delete();
}
}

Il est difficile de faire plus concis. Le seul élément qui va nous indiquer de quel modèle il s'agit est la propriété$model. Par contre le reste est tout à fait anonyme. Du coup le repository pour les utilisateurs va se trouver très simple à écrire :

<?php
namespace App\Repositories;
use App\User;
class UserRepository extends ResourceRepository
{
public function __construct(User $user)
{
$this->model = $user;
}
}

On a toutefois quelques petits soucis. Par exemple on a vu qu'il faut chiffrer le mot de passe et on réalisait cela dans le repository. On pourrait surcharger la méthode  store pour le prévoir mais ça casserait la jolie harmonie du code.

Le modèle

Une autre solution plus élégante est de prévoir un "mutator" au niveau du modèle. Mais voyons déjà ce que nous avons dans ce modèleUser :

<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes excluded from the model's JSON form.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
}

Je reviendrai plus en détail sur les traits utilisés quand on verra l'authentification. Pour le moment on va s'intéresser à la propriété  $fillable. Lorsqu'on crée un enregistrement avec  la méthodecreate comme on l'a prévu ci-dessus il y a un risque au niveau de la sécurité. Dans le tableau transmis en paramètre on a normalement seulement les champs que l'on désire renseigner. Mais si un petit malin envoie d'autres informations elles risquent fort de se propager jusqu'à la table. Pour éviter cela on définit dans le modèle les champs qui peuvent être mis à jour avec cette méthode (on parle de mise à jour de masse) dans la propriété  $fillable. Comme on a aussi le champ  admin à renseigner il faut compléter le tableau :

<?php
protected $fillable = ['name', 'email', 'password', 'admin'];

On veut également chiffrer le mot de passe. Il suffit de mettre en place ce "mutator" :

<?php
public function setPasswordAttribute($password)
{
$this->attributes['password'] = bcrypt($password);
}

Ainsi chaque fois qu'on va assigner l'attribut  password il passera par cette méthode et on aura un cryptage de la valeur.

Le contrôleur

Il nous ne reste plus que la gestion de la case à cocher pour l'administration. On ne peut pas le résoudre comme on l'a fait pour le mot de passe puisque l'on n'a pas toujours l'information (la case à cocher n'est transmise que si elle est cochée). On va donc prévoir ce traitement dans le contrôleur :

<?php
namespace App\Http\Controllers;
use App\Http\Requests\UserCreateRequest;
use App\Http\Requests\UserUpdateRequest;
use App\Repositories\UserRepository;
use Illuminate\Http\Request;
class UserController extends Controller
{
protected $userRepository;
protected $nbrPerPage = 4;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function index()
{
$users = $this->userRepository->getPaginate($this->nbrPerPage);
$links = $users->render();
return view('index', compact('users', 'links'));
}
public function create()
{
return view('create');
}
public function store(UserCreateRequest $request)
{
$this->setAdmin($request);
$user = $this->userRepository->store($request->all());
return redirect('user')->withOk("L'utilisateur " . $user->name . " a été créé.");
}
public function show($id)
{
$user = $this->userRepository->getById($id);
return view('show', compact('user'));
}
public function edit($id)
{
$user = $this->userRepository->getById($id);
return view('edit', compact('user'));
}
public function update(UserUpdateRequest $request, $id)
{
$this->setAdmin($request);
$this->userRepository->update($id, $request->all());
return redirect('user')->withOk("L'utilisateur " . $request->input('name') . " a été modifié.");
}
public function destroy($id)
{
$this->userRepository->destroy($id);
return redirect()->back();
}
private function setAdmin($request)
{
if(!$request->has('admin'))
{
$request->merge(['admin' => 0]);
}
}
}

C'est la fonction privée  setAdmin qui est chargée de gérer la case à cocher. On teste qu'on n'a pas la clé  admin dans le tableau de données et, si c'est le cas, on l'ajoute ‌en utilisant la méthode  merge.

On en arrive à un code plus clair et plus facile à réutiliser et à maintenir. On pourrait pousser la réflexion au niveau du contrôleur et créer un contrôleur de base pour les ressources. Il suffirait de prévoir un préfixe pour les vues et de réfléchir aux injections, et on pourrait obtenir quelque chose de très élégant, mais je ne vais pas poursuivre cette réflexion pour ne pas alourdir ce chapitre, l'important est de comprendre le principe. D'autre part on arrive rapidement à des impasses dans une application réelle qui oblige souvent à multiplier les codes spécifiques.

Les erreurs

La gestion des erreurs constitue une part importante dans le développement d'une application. On dit parfois que c'est dans ce domaine qu'on fait la différence entre le professionnel et l'amateur. Que nous propose Laravel dans ce domaine ?

Puisque nous sommes dans l'accès aux données il peut arriver un souci avec la connexion à la base. Voyons un peu ce que nous obtenons si MySQL ne répond plus... Avec notre application si j'arrête le service de MySQL puis que je lance l'url :

http://monsite.fr/user

J'obtiens :

Erreur suite à arrêt de MySQL

Je n'ai affiché ici que la partie supérieure. Ne soyez pas impressionné par la quantité de messages de la page d'erreurs, avec un peu d'habitude vous serez heureux de disposer de tous ces renseignements lorsqu'une erreur se produit dans votre application.

La première question qu'on peut se poser est : cet affichage des erreurs est parfait pour la phase de développement mais sur une application en ligne il vaut mieux cacher tout ça pour deux raisons évidentes : l'utilisateur n'en a rien à faire, par contre ça pourrait servir à quelqu'un de mal intentionné et lui fournir de précieux renseignements sur le fonctionnement de vos scripts.

Si vous regardez dans le fichier.env dont je vous ai déjà parlé vous trouvez cette ligne :

APP_DEBUG=true

Si on metfalse ici pour voir la différence on obtient plus que le laconique :

Le message d'erreur de Laravel
Le message d'erreur simplifié de Laravel

Maintenant c'est plus sommaire ! Mais pour le coup ça devient trop laconique pour l'utilisateur et surtout ça ne lui donne aucun lien pour accéder à une autre page. Vous pouvez aussi considérer qu'un message en anglais n'est pas adapté pour votre site francophone. Malheureusement la page correspondante se trouve dans le framework qu'on ne va évidemment pas aller bricoler !

Regardez ce fichier :

 

Le fichier des erreurs

Avec ce code :

<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that should not be reported.
*
* @var array
*/
protected $dontReport = [
AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
ValidationException::class,
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Exception $e
* @return void
*/
public function report(Exception $e)
{
return parent::report($e);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
return parent::render($request, $e);
}
}

Toutes les erreurs d'exécution sont traitées ici. On découvre que tout est archivé avec la méthode report. Mais où se trouve cet archivage ? Regardez dansstorage/logs, vous trouvez un fichier laravel.log. En l'ouvrant vous trouvez les erreurs archivées avec leur trace. Par exemple dans notre cas on a bien :

[2015-12-28 17:09:42] local.ERROR: exception 'PDOException' with message 'SQLSTATE[HY000] [2002] Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée..

C'est important de disposer de ce genre d'archivage des erreurs sur une application en production.

Un cas fréquent est celui de la page non trouvée (erreur 404) :

L'erreur 404
L'erreur 404

Si on veut changer le message de Laravel pour le rendre à notre goût et surtout adapté à notre langue il faut prévoir une vue. Par exemple (resources/views/errors/404.blade.php) :

@extends('template')
@section('contenu')
<br>
<div class="col-sm-offset-4 col-sm-4">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">Il y a un problème !</h3>
</div>
<div class="panel-body">
<p>Nous sommes désolés mais la page que vous désirez n'existe pas...</p>
</div>
</div>
</div>
@endsection

Maintenant pour une url qui n'aboutit pas on obtient :

Notre vue d'erreur 404 personnalisée

Avouez que c'est quand même mieux et Laravel est assez intelligent pour aller chercher cette vue automatiquement sans qu'on ait quelque chose de particulier à faire !

On a vu dans ce chapitre que lorsqu'on arrête le service MySql on génère une erreur PDOException. Comment intercepter cette erreur ? Voilà une solution :

<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
...
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $e
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
if($e instanceof \PDOException)
{
return response()->view('errors.pdo', [], 500);
}
return parent::render($request, $e);
}
}

Ainsi on affichera une page spécifique pour cette erreur.

En résumé

  • Créer un gestionnaire (repository) indépendant du contrôleur pour les accès aux données permet de disposer d'un code clair et facile à maintenir et tester.

  • Il est important de penser à la réutilisation du code que l'on crée.

  • Les vues doivent utiliser au maximum les possibilités de Blade, des helpers et de la classe Form pour être concises et lisibles.

  • La gestion des erreurs ne doit pas être négligée, il faut enlever le mode de débogage sur un site en production et prévoir des messages explicites pour les utilisateurs en fonction de l'erreur rencontrée.

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