Cours Laravel 5.5 – les données – les ressources
Dimanche 1 octobre 2017 19:01
Avec Laravel on peut créer facilement un routage et un contrôleur REST/CRUD, autrement appelé contrôleur de ressource. Il est aussi aisé de bâtir des API et de travailler avec du JSON. Dans ce chapitre je vous propose d'explorer cet aspect de Laravel.
Préparation
Pour ce chapitre on va partir d'une installation vierge de Laravel :composer create-project --prefer-dist laravel/laravel laravel5
Complétée par l'authentification :
php artisan make:auth
Et par une migration (avec la bonne configuration dans le fichier .env) :
php artisan migrate
Vous devez ainsi avoir un Laravel fonctionnel :
Et les tables dans la base :
On va utiliser tinker pour créer quelques utilisateurs :
php artisan tinker
Psy Shell v0.8.11 (PHP 7.1.9 — cli) by Justin Hileman
>>> factory(App\User::class, 10)->create()
=> Illuminate\Database\Eloquent\Collection {#765
all: [
App\User {#761
name: "Rigoberto Balistreri Sr.",
email: "xstroman@example.com",
updated_at: "2017-09-30 22:03:29",
created_at: "2017-09-30 22:03:29",
id: 1,
},
...
J'ai déjà parlé des factories dans un précédent chapitre.
On utilise alors la méthode create d'Eloquent pour enregistrer les données dans la base :
Vous voyez ce qu'on peut faire avec juste une ligne dans tinker !
Les contrôleurs de ressource
Le contrôleur
Lorsque vous créez un contrôleur vous devez déterminer le nom des méthodes, les créer puis les coder. Un contrôleur de ressource peut bien vous aider dans cette tâche en normalisant (REST/CRUD) les méthodes. Pour créer un contrôleur de ressource c'est tout simple :php artisan make:controller UserController --resource
Vous avez alors la création du contrôleur comme on l'a déjà vu dans ce cours :
Mais le fait d'ajouter l'option --resource crée ce code :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
On se retrouve avec 7 méthodes correspondant aux tâches classiques :
- index : afficher la liste des utilisateurs
- create : afficher le formulaire pour la création d'un utilisateur
- store : créer un utilisateur
- show : afficher l'utilisateur
- edit : afficher le formulaire pour la modification d'un utilisateur
- update : modifier un utilisateur
- destroy : supprimer un utilisateur
Les routes
Pour générer les routes vers les 7 méthodes du contrôleur de ressource c'est aussi très simple. Entrez ce code dans le fichier routes/web.php :Route::resource('users', 'UserController');
Si maintenant vous regardez les routes générées avec :
php artisan route:list
Vous obtenez ceci :
On a 7 routes, chacune correspondant à une méthode du contrôleur :
Verbe | URL | Méthode du contrôleur | Nom | Description |
GET | users | index() | users.index | Liste de tous les utilisateurs |
GET | users/create | create() | users.create | Affichage formulaire de création |
POST | users | store() | users.store | Création de l'utilisateur |
GET | users/{user} | show() | users.show | Affichage de l'utilisateur |
GET | users/{user}/edit | edit() | users.edit | Affichage formulaire de modification |
PUT/PATCH | users/{user} | update() | users.update | Modification de l'utilisateur |
DELETE | users/{user} | destroy() | users.destroy | Suppression de l'utilisateur |
Un peu de code
Liste des utilisateurs
Maintenant qu'on a un contrôleur et des routes dédiées il suffit de compléter le code. On va commencer par déclarer l'espace de nom pour le modèle pour simplifier la syntaxe :use App\User;
Ensuite on code la méthode index :
public function index()
{
$users = User::all();
foreach ($users as $user) {
echo $user->name . '<br>';
}
}
Et pour l'url .../users on obtient quelque chose dans ce genre :
Rigoberto Balistreri Sr.
Noble Fisher Sr.
Brenna Mohr
Helen Graham
Gardner Trantow
Adrianna Kautzer
Eladio Douglas IV
Susie Koch
Mr. Ned Harvey V
Kareem Hirthe
Afficher un utilisateur
C'est la méthode show qui est concernée :public function show(User $user)
{
echo 'Nom :' . $user->name . '<br>';
echo 'Email :' . $user->email . '<br>';
}
On a déjà vu dans un précédent chapitre la liaison implicite entre le paramètre dans l'url et un modèle.
Avec une url du genre ...users/1 on obtient :Nom :Rigoberto Balistreri Sr.
Email :xstroman@example.com
Créer un utilisateur
Pour la création d'un utilisateur deux méthodes sont concernées :public function create()
{
return view('create');
}
public function store(Request $request)
{
User::create($request->all());
return "Utilisateur créé !";
}
Pour simplifier je n'ai pas prévu de validation.
Voici la vue create :@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Créer un utilisateur</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('users.store') }}">
{{ csrf_field() }}
<div class="form-group">
<label for="name" class="col-md-4 control-label">Nom</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control" name="name" required autofocus>
</div>
</div>
<div class="form-group">
<label for="email" class="col-md-4 control-label">E-Mail</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" required>
</div>
</div>
<div class="form-group">
<label for="password" class="col-md-4 control-label">Mot de passe</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>
</div>
</div>
<div class="form-group">
<div class="col-md-8 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Enregistrer
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Avec cet aspect (en utilisant le template par défaut de Laravel) en utilisant l'url .../users/create :
Remarquez dans le formulaire l'utilisation du verbe POST et la génération de l'action à partir du nom de la route :
<form class="form-horizontal" method="POST" action="{{ route('users.store') }}">
Modifier un utilisateur
Pour la modification d'un utilisateur ce sont ces deux méthodes :public function edit(User $user)
{
return view('edit', compact('user'));
}
public function update(Request $request, User $user)
{
$user->update($request->all());
return "Utilisateur modifié !";
}
Pour simplifier je n'ai pas prévu de validation.
Voici la vue edit :@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Modifier un utilisateur</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('users.update', $user->id) }}">
{{ csrf_field() }}
{{ method_field('PUT') }}
<div class="form-group">
<label for="name" class="col-md-4 control-label">Nom</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control" name="name" value="{{ $user->name }}" required autofocus>
</div>
</div>
<div class="form-group">
<label for="email" class="col-md-4 control-label">E-Mail</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email" value="{{ $user->email }}" required>
</div>
</div>
<div class="form-group">
<div class="col-md-8 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Enregistrer
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
On a du code commun avec la vue pour la création et dans un projet réel il serait judicieux de mutualiser ce code dans un template.
Avec cet aspect (en utilisant le template par défaut de Laravel) en utilisant l'url .../users/1/edit : Remarquez dans le formulaire la génération de l'action à partir du nom de la route (avec transmission de l'identifiant de l'utilisateur en paramètre) :<form class="form-horizontal" method="POST" action="{{ route('users.update', $user->id) }}">
D'autre part, comme on l'a déjà vu dans un précédent chapitre, le navigateur ne sait pas gérer le verbe PUT , donc on lui indique un verbe POST mais on ajoute un champ caché en précisant le bon verbe :
{{ method_field('PUT') }}
Supprimer un utilisateur
C'est la méthode destroy qui est concernée :public function destroy(User $user)
{
$user->delete();
return 'Utilisateur supprimé !';
}
Avec une url du genre ...users/1 et le verbe DELETE on supprime l'utilisateur correspondant.
Pour voir ça en action on va ajouter une route :
Route::get('users/{user}/destroy', 'UserController@destroyForm');
Une méthode dans le contrôleur :
public function destroyForm(User $user)
{
return view('destroy', compact('user'));
}
Et cette vue destroy :
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Supprimer un utilisateur</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('users.destroy', $user->id) }}">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<div class="form-group">
<div class="col-md-8 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Supprimer l'utilisateur {{ $user->name }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
Avec cet aspect pour une url dans le genre ...users/1/destroy :
L'application d'exemple
Dans l'application d'exemple on utilise évidemment des ressources comme on l'a vu ci-dessus mais pas toujours de façon complète. Par exemple pour les contacts on utilise seulement (only) les méthodes create et store pour le front :Route::resource('contacts', 'Front\ContactController', ['only' => ['create', 'store']]);
Et les méthodes index et destroy pour le back :
Route::resource('contacts', 'ContactController', ['only' => ['index', 'destroy']]);
Pour les commentaires on utilise seulement (only) les méthodes update et destroy pour le front :
Route::resource('comments', 'Front\CommentController', ['only' => ['update', 'destroy']]);
Et les méthodes index et destroy pour le back :
Route::resource('comments', 'CommentController', ['only' => ['index', 'destroy']]);
Pour les utilisateur dans le back on utilise seulement (only) les méthodes index, edit, update et destroy :
Route::resource('users', 'UserController', ['only' => ['index', 'edit', 'update', 'destroy']]);
On pourrait adopter la logique inverse et utiliser except au lieu de only.
Par contre pour les articles en back la ressource est complète :Route::resource('posts', 'PostController');
Classiquement avec la méthode index on affiche la liste paginée de la ressource avec des boutons permettant les opérations de modification ou suppression. Par exemple dans l'administration pour les utilisateurs on a :
Pour chaque utilisateur on affiche les données et on a deux boutons à droite pour la modification et la suppression. Le premier est un simple lien du genre ...admin/users/1/edit alors que le second est traité en Ajax.
Pour les articles on affiche 3 boutons :
On a en plus le bouton (vert) pour visualiser l'article.
Les API REST
REST (Representational State Transfer) est un style d'architecture pour constituer des API. Pour Laravel une API REST est :- organisée autours de ressources
- sans persistance (aucune session)
- orientée client-serveur
- pouvant être mis en cache (donc une requête doit retourner toujours les mêmes résultats)
- retourner du JSON...
GET /api/users
[
{
id: 1,
name: 'Rigoberto Balistreri Sr. Noble Fisher Sr.'
},
{
id: 2,
name: 'Noble Fisher Sr.'
}
...
]
Ou ce endpoint pour les informations concernant un utilisateur :
GET /api/users/2
{
id: 2,
name: 'Noble Fisher Sr.'
}
On peut imaginer d'autres endpoints pour modifier, ajouter, supprimer...
Il ne reste plus qu'à voir comment on réalise ça avec Laravel mais vous en avez désormais une idée déjà assez précise avec ce qu'on a déjà vu dans ce chapitre.
Le contrôleur
On va créer le contrôleur :php artisan make:controller Api/UserController --resource
On le retrouve rangé dans le dossier app/Http/Controllers/Api créé par la même occasion :
Avec évidemment le même code que celui qu'on a eu pour la même commande ci-dessus (et Laravel se débrouille tout seul pour les espaces de nom) :
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
On va supprimer les méthodes create et edit qui n'ont pas lieu d'être pour une API.
Et on va coder les autres méthodes :
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\User;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return User::all();
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
User::create($request->all());
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show(User $user)
{
return $user;
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, User $user)
{
$user->update($request->all());
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(User $user)
{
$user->delete();
}
}
Evidemment ça ressemble énormément à ce qu'on a fait précédemment et c'est normal puisqu'il s'agit du même genre de traitement sur les mêmes données.
Les routes
Pour les routes on adopte aussi une ressource dans le fichier routes/api.php :Route::namespace('Api')->group(function() {
Route::resource('users', 'UserController', ['except' => ['create', 'edit']]);
});
Ce qui donne ces routes :
Le fait de déclarer ces routes dans le fichier routes/api.php a pour effet de leur appliquer le middleware api au lieu de web. Si vous regardez dans le fichier app/Http/Kernel.php vous allez voir la différence que ça génère :
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
Comme on est sans état plus de cookie, de session, de protection CSRF...
Le fonctionnement
Maintenant avec l'url ...api/users on obtient une réponse JSON (Laravel convertit automatiquement la réponse en JSON sans qu'on lui demande) : On obtient toute sles colonnes non cachées, c'est à dire celles qui ne sont pas définies dans la propriété $hidden du modèle :protected $hidden = [
'password', 'remember_token',
];
Avec l'url .../api/users/2 on obtient :
On pourrait aussi imaginer utiliser une pagination pour cette API. Il suffit de changer la méthode index :
public function index()
{
return User::paginate(4);
}
On peut alors demander la page qu'on veut, par exemple la seconde avec l'url ...api/users?page=2 :
On ne reçoit que les informations de la page 2 et en plus des renseignements concernant la pagination.
On peut imaginer aussi filtrer, ordonner...
Je ne traite pas dans ce chapitre de l'authentification spécifique aux API. Je vous invite à regarder la documentation de Passport sur le sujet.
Les ressources d'API
On a vu ci-dessus comme il est facile de retrouner une réponse JSON à partir des données d'un modèle. C'est parfait tant qu'on veut une simple sérialisation des valeurs des colonnes. Mais parfois on veut effectuer quelques traitements intermédiaires (ça correspond au design pattern transformer). Imaginons que nous voulons retourner seulement des deux premières lettres des noms, c'est une idée bizarre mais pourquoi pas ? On utilise Artisan pour créer la ressource :php artisan make:resource User
On se retrouve avec la ressource ici :
Avec ce code :
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class User extends Resource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}
Là on se contente de tout sérialiser, ça ne va donc pas changer grand chose à ce qu'on avait précédemment. Dans le contrôleur on utilise la ressource, par exemple pour la méthode show :
use App\Http\Resources\User as UserResource;
...
public function show(User $user)
{
return new UserResource($user);
}
Maintenant pour l'url .../api/users/2 on obtient :
Maintenant changeons la ressource :
public function toArray($request)
{
return [
'id' => $this->id,
'name' => substr($this->name, 0, 2),
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
Maintenant pour l'url .../api/users/2 on obtient :
On voit qu'on n'a plus que les deux premiers caractères du nom. On a bien effectué un traitement intermédiaire sur les valeurs retournées.
Il y aurait encore beaucoup à dire sur les ressources et les API mais vous avez maintenant une bonne base de départ.
En résumé
- Un contrôleur de ressource permet de normaliser les opérations CRUD. Sa création et son routage sont très simples.
- Laravel permet de créer facilement une API et de permettre des transformations avec des ressources.
Par bestmomo
Nombre de commentaires : 2