Tutoriel pour apprendre à créer une application avec Laravel 5

Routes, middlewares et commandes

Maurice Chavelli

Dans le précédent tutoriel, nous avons vu comment sont organisées les données. Nous allons voir à présent comment sont gérées les requêtes entrantes. Nous allons passer en revue les routes utilisées et les middlewares. Nous verrons aussi la notion nouvelle de « command bus ».

Commentez

- Titre : Tutoriel pour apprendre à créer une application avec Laravel 5

- Auteur : Maurice Chavelli

- Parution : 11 septembre 2017

- Licence : Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Les routes

- III. Les middlewares

- III-A. isAdmin

- III-B. isRedactor

- III-C. isAjax

- III-D. Déclaration des middlewares

- IV. Les commandes

- V. Remerciements

I - Introduction

La notion de middleware (logiciel médiateur, intergiciel) est nouvelle dans le monde de Laravel. Il faut le voir comme une couche entre la requête et l'application. Voici une représentation pour aider à la compréhension :

Le but d'un middleware est donc d'agir sur la requête ou d'y lire des informations pour adapter la configuration de l'application en conséquence. L'application utilise trois middlewares spécifiques comme nous allons le voir dans ce tutoriel.

II - Les routes

Voici la totalité des routes obtenues avec la commande php artisan route:list :

Vous pouvez cliquer sur l'image pour en obtenir un agrandissement.

Voici le fichier app/Http/route.php :

1. <?php

3. Route::group(['middleware' => ['web']], function () {

5. // Home

6. Route::get('/', [

7. 'uses' => 'HomeController@index',

8. 'as' => 'home'

9. ]);

10. Route::get('language/{lang}', 'HomeController@language')->where('lang', '[A-Za-z_-]+');

13. // Admin

14. Route::get('admin', [

15. 'uses' => 'AdminController@admin',

16. 'as' => 'admin',

17. 'middleware' => 'admin'

18. ]);

20. Route::get('medias', [

21. 'uses' => 'AdminController@filemanager',

22. 'as' => 'medias',

23. 'middleware' => 'redac'

24. ]);

27. // Blog

28. Route::get('blog/order', ['uses' => 'BlogController@indexOrder', 'as' => 'blog.order']);

29. Route::get('articles', 'BlogController@indexFront');

30. Route::get('blog/tag', 'BlogController@tag');

31. Route::get('blog/search', 'BlogController@search');

33. Route::put('postseen/{id}', 'BlogController@updateSeen');

34. Route::put('postactive/{id}', 'BlogController@updateActive');

36. Route::resource('blog', 'BlogController');

38. // Comment

39. Route::resource('comment', 'CommentController', [

40. 'except' => ['create', 'show']

41. ]);

43. Route::put('commentseen/{id}', 'CommentController@updateSeen');

44. Route::put('uservalid/{id}', 'CommentController@valid');

47. // Contact

48. Route::resource('contact', 'ContactController', [

49. 'except' => ['show', 'edit']

50. ]);

53. // User

54. Route::get('user/sort/{role}', 'UserController@indexSort');

56. Route::get('user/roles', 'UserController@getRoles');

57. Route::post('user/roles', 'UserController@postRoles');

59. Route::put('userseen/{user}', 'UserController@updateSeen');

61. Route::resource('user', 'UserController');

63. // Authentication routes...

64. Route::get('auth/login', 'Auth\AuthController@getLogin');

65. Route::post('auth/login', 'Auth\AuthController@postLogin');

66. Route::get('auth/logout', 'Auth\AuthController@getLogout');

67. Route::get('auth/confirm/{token}', 'Auth\AuthController@getConfirm');

69. // Resend routes...

70. Route::get('auth/resend', 'Auth\AuthController@getResend');

72. // Registration routes...

73. Route::get('auth/register', 'Auth\AuthController@getRegister');

74. Route::post('auth/register', 'Auth\AuthController@postRegister');

76. // Password reset link request routes...

77. Route::get('password/email', 'Auth\PasswordController@getEmail');

78. Route::post('password/email', 'Auth\PasswordController@postEmail');

80. // Password reset routes...

81. Route::get('password/reset/{token}', 'Auth\PasswordController@getReset');

82. Route::post('password/reset', 'Auth\PasswordController@postReset');

84. });

Il y a des routes élémentaires avec les verbes get, put et post :

1. Route::get('/', [

2. 'uses' => 'HomeController@index',

3. 'as' => 'home'

4. ]);

6. Route::put('postseen/{id}', 'BlogController@updateSeen');

8. Route::post('user/roles', 'UserController@postRoles');

On a enfin des ressources pour les articles, les commentaires, les contacts et les utilisateurs :

1. Route::resource('blog', 'BlogController');

3. Route::resource('comment', 'CommentController', [

4. 'except' => ['create', 'show']

5. ]);

7. Route::resource('contact', 'ContactController', [

8. 'except' => ['show', 'edit']

9. ]);

11. Route::resource('user', 'UserController');

Pour mémoire, la commande resource permet de créer toutes les routes RestFull. Si on regarde par exemple pour le blogue avec la simple ligne prévue, on se retrouve avec toutes ces routes :

On bénéficie en plus du nommage standardisé de ces routes.

III - Les middlewares

Il est prévu trois middlewares pour l'application :

III-A - isAdmin

Ce middleware a pour but de vérifier que l'utilisateur connecté est un administrateur, ce qui sera utile pour autoriser l'accès à la zone d'administration. En voici le code :

1. <?php namespace App\Http\Middleware;

3. use Closure;

4. use Illuminate\Http\RedirectResponse;

6. class IsAdmin {

8. /**

9. * Handle an incoming request.

10. *

11. * @param  \Illuminate\Http\Request  $request

12. * @param  \Closure  $next

13. * @return mixed

14. */

15. public function handle($request, Closure $next)

16. {

17. $user = $request->user();

19. if ($user && $user->isAdmin())

20. {

21. return $next($request);

22. }

23. return new RedirectResponse(url('/'));

24. }

26. }

En gros un middleware reçoit la requête, effectue son traitement, puis transmet la requête au middleware suivant, ou à l'application si c'est le dernier.

Ici on utilise la méthode isAdmin qu'on a prévue dans le modèle User (que nous avons vue lors du précédent tutoriel). Si c'est un administrateur on continue de propager la requête. Dans le cas contraire on redirige sur la page d'accueil.

Ce middleware est appliqué dans la route qui dirige vers l'administration :

1. Route::get('admin', [

2. 'uses' => 'AdminController@admin',

3. 'as' => 'admin',

4. 'middleware' => 'admin'

5. ]);

Mais on le retrouve aussi dans certains contrôleurs comme CommentController, appliqué à certaines méthodes réservées aux administrateurs :

$this->middleware('admin', ['except' => ['store', 'edit', 'update', 'destroy']]);

Pour voir rapidement partout où il est utilisé, il suffit de regarder le tableau généré par php artisan route:list que nous avons vu ci-dessus.

III-B - isRedactor

Ce middleware ressemble beaucoup au précédent. Le but est de savoir si l'utilisateur connecté est au moins un rédacteur (parce qu'il est évident qu'un administrateur aura aussi les droits correspondants). Voici le code :

1. <?php namespace App\Http\Middleware;

3. use Closure;

4. use Illuminate\Http\RedirectResponse;

6. class IsRedactor {

8. /**

9. * Handle an incoming request.

10. *

11. * @param  \Illuminate\Http\Request  $request

12. * @param  \Closure  $next

13. * @return mixed

14. */

15. public function handle($request, Closure $next)

16. {

17. $user = $request->user();

19. if ($user && $user->isNotUser())

20. {

21. return $next($request);

22. }

23. return new RedirectResponse(url('/'));

24. }

26. }

Cette fois on utilise la méthode isNotUser du modèle User. En effet, si ce n'est pas un simple utilisateur c'est soit un rédacteur, soit un administrateur, donc exactement ce qu'on veut.

Ce middleware est utilisé dans la route des médias :

1. Route::get('medias', [

2. 'uses' => 'AdminController@filemanager',

3. 'as' => 'medias',

4. 'middleware' => 'redac'

5. ]);

En effet seuls les rédacteurs et les administrateurs sont autorisés à gérer les médias.

On trouve aussi ce middleware dans le constructeur de certains contrôleurs comme dans BlogController pour certaines fonctions :

$this->middleware('redac', ['except' => ['indexFront', 'show', 'tag', 'search']]);

Pour voir rapidement partout où il est utilisé, comme nous l'avons vu pour le précédent middleware, il suffit de regarder le tableau généré par php artisan route:list, que nous avons vu ci-dessus.

III-C - isAjax

Ce middleware ne concerne pas les rôles comme les deux précédents, mais la nature de la requête. On veut vérifier que celle-ci est en Ajax. Voici le code :

1. <?php namespace App\Http\Middleware;

3. use Closure;

5. class IsAjax {

7. public function handle($request, Closure $next)

8. {

9. if ($request->ajax())

10. {

11. return $next($request);

12. }

14. abort(404);

15. }

17. }

La méthode ajax de la classe Request permet facilement de le vérifier. Si la requête n'est pas en Ajax on affiche une erreur 404. Laravel 5 permet facilement d'utiliser une vue spécifique pour cette erreur, il suffit de la placer dans resources/views/errors et de la nommer correctement :

III-D - Déclaration des middlewares

Il ne suffit pas de créer un middleware pour qu'il soit connu par Laravel, il faut le déclarer dans app/Http/Kernel.php :

1. protected $routeMiddleware = [

2. 'auth' => \App\Http\Middleware\Authenticate::class,

3. 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,

4. 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,

5. 'admin' => \App\Http\Middleware\IsAdmin::class,

6. 'redac' => \App\Http\Middleware\IsRedactor::class,

7. 'ajax' => \App\Http\Middleware\IsAjax::class

8. ];

On trouve ici les trois middlewares ajoutés à Laravel.

IV - Les commandes

La version 5 de Laravel a également vu l'arrivée des commandes. Non pas celles d'Artisan que nous avions déjà, mais un nouveau concept qui permet de gérer différemment le code si on le désire. De quoi s'agit-il ?

Le « command bus » de Laravel est l'application d'un pattern bien connu. Il consiste à séparer les actions à effectuer en tâches élémentaires. Pour chacune d'elles, vous créez un objet de commande que vous envoyez dans le « bus » et ensuite vous savez que la commande aboutira et sera exécutée.

Il y a un dossier appelé Jobs dédié à ces commandes :

Dans notre application j'en ai prévu deux pour gérer la localisation. Regardons le code de SetLocale :

1. <?php

3. namespace App\Jobs;

5. use App\Jobs\Job;

6. use Request;

8. class SetLocale extends Job

9. {

10. /**

11. * Execute the command.

12. *

13. * @return void

14. */

15. public function handle()

16. {

18. if(!session()->has('locale'))

19. {

20. session()->put('locale', Request::getPreferredLanguage( config('app.languages') ));

21. }

23. app()->setLocale(session('locale'));

24. }

25. }

On fait une action simple : définir la locale selon l'utilisateur. Si on a déjà une valeur dans la session, on l'utilise, sinon on pioche l'information dans la requête avec la méthode getPreferredLanguage. Ensuite on définit la locale de l'application.

La seconde commande est encore plus simple :

1. <?php

3. namespace App\Jobs;

5. use App\Jobs\Job;

6. use Illuminate\Contracts\Bus\SelfHandling;

8. class ChangeLocale extends Job implements SelfHandling

9. {

10. /**

11. * Execute the job.

12. *

13. * @return void

14. */

15. public function handle()

16. {

17. session()->set('locale', $this->lang);

18. }

19. }

Ici on se contente de changer la locale.

Maintenant la question est : d'où appelle-t-on ces commandes ? Regardez le middleware App :

1. <?php

3. namespace App\Http\Middleware;

5. use Closure;

7. use App\Jobs\SetLocale;

9. use Illuminate\Bus\Dispatcher as BusDispatcher;

10. use App\Events\UserAccess;

12. class App

13. {

15. /**

16. * The command bus.

17. *

18. * @array $bus

19. */

20. protected $bus;

22. /**

23. * The command bus.

24. *

25. * @array $bus

26. */

27. protected $setLocale;

29. /**

30. * Create a new App instance.

31. *

32. * @param  Illuminate\Bus\Dispatcher $bus

33. * @param  App\Jobs\SetLocaleCommand $setLocaleCommand

34. * @return void

35. */

36. public function __construct(

37. BusDispatcher $bus,

38. SetLocale $setLocale)

39. {

40. $this->bus = $bus;

41. $this->setLocale = $setLocale;

42. }

44. /**

45. * Handle an incoming request.

46. *

47. * @param  Illuminate\Http\Request  $request

48. * @param  Closure  $next

49. * @return mixed

50. */

51. public function handle($request, Closure $next)

52. {

53. $this->bus->dispatch($this->setLocale);

55. event(new UserAccess);

57. return $next($request);

58. }

60. }

Ce middleware App est présent à l'installation de Laravel 5. C'est un bon emplacement pour faire des initialisations. Ici on va lancer la commande setLocale pour initialiser la locale lorsqu'une requête arrive. Vous pouvez voir comme c'est simplement réalisé.

Remarquez au passage qu'on déclenche aussi l'événement UserAccess. Nous verrons dans un chapitre ultérieur les événements. Ici nous fixerons grâce à cet événement le statut de l'utilisateur selon son rôle.

La commande ChangeLocale n'est évidemment pas utilisée au démarrage de l'application, mais uniquement lorsque l'utilisateur désire changer la langue de l'interface. Pour ça il dispose d'un petit drapeau dans la barre de menu :

Ce drapeau change d'aspect selon la langue actuelle. Si vous regardez dans le contrôleur HomeController vous trouvez le code correspondant :

1. /**

2. * Change language.

3. *

4. * @param  App\Jobs\ChangeLocaleCommand $changeLocaleCommand

5. * @return Response

6. */

7. public function language(

8. ChangeLocale $changeLocale)

9. {

10. ...

11. $this->dispatch($changeLocale);

12. return redirect()->back();

13. }

C'est ici qu'on utilise cette commande pour changer la locale.

J'ai réalisé cette partie avec des commandes, mais j'aurais pu le faire avec un service, ou d'une autre manière encore. Laravel permet de réaliser une application de plusieurs façons selon les goûts de chacun !

V - Remerciements

Nous remercions Maurice Chavelli qui nous a autorisés à publier ce tutoriel.

Nous tenons également à remercier Winjerome pour la gabarisation et Maxy35 pour la relecture orthographique.