Laravel 5

Créer une application : routes, middlewares et commandes

Chapitre mis à jour le 23/04/2016

Dans le précédent article 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 ».

La notion de middleware 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 :

img29

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 3 middlewares spécifiques comme nous allons le voir dans cet article.

Les routes

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

img35

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

Voici le fichier app/Http/route.php :

<?php

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

    // Home
    Route::get('/', [
        'uses' => 'HomeController@index', 
        'as' => 'home'
    ]);
    Route::get('language/{lang}', 'HomeController@language')->where('lang', '[A-Za-z_-]+');


    // Admin
    Route::get('admin', [
        'uses' => 'AdminController@admin',
        'as' => 'admin',
        'middleware' => 'admin'
    ]);

    Route::get('medias', [
        'uses' => 'AdminController@filemanager',
        'as' => 'medias',
        'middleware' => 'redac'
    ]);


    // Blog
    Route::get('blog/order', ['uses' => 'BlogController@indexOrder', 'as' => 'blog.order']);
    Route::get('articles', 'BlogController@indexFront');
    Route::get('blog/tag', 'BlogController@tag');
    Route::get('blog/search', 'BlogController@search');

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

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

    // Comment
    Route::resource('comment', 'CommentController', [
        'except' => ['create', 'show']
    ]);

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


    // Contact
    Route::resource('contact', 'ContactController', [
        'except' => ['show', 'edit']
    ]);


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

    Route::get('user/roles', 'UserController@getRoles');
    Route::post('user/roles', 'UserController@postRoles');

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

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

    // Authentication routes...
    Route::get('auth/login', 'Auth\AuthController@getLogin');
    Route::post('auth/login', 'Auth\AuthController@postLogin');
    Route::get('auth/logout', 'Auth\AuthController@getLogout');
    Route::get('auth/confirm/{token}', 'Auth\AuthController@getConfirm');

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

    // Registration routes...
    Route::get('auth/register', 'Auth\AuthController@getRegister');
    Route::post('auth/register', 'Auth\AuthController@postRegister');

    // Password reset link request routes...
    Route::get('password/email', 'Auth\PasswordController@getEmail');
    Route::post('password/email', 'Auth\PasswordController@postEmail');

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

});

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

Route::get('/', [
	'uses' => 'HomeController@index', 
	'as' => 'home'
]);

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

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

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

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

Route::resource('comment', 'CommentController', [
	'except' => ['create', 'show']
]);

Route::resource('contact', 'ContactController', [
	'except' => ['show', 'edit']
]);

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 blog avec la simple ligne prévue on se retrouve avec toutes ces routes :

img18

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

Les middlewares

Il est prévu 3 middlewares pour l’application :

img32

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 :

<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\RedirectResponse;

class IsAdmin {

	/**
	 * Handle an incoming request.
	 *
	 * @param  \Illuminate\Http\Request  $request
	 * @param  \Closure  $next
	 * @return mixed
	 */
	public function handle($request, Closure $next)
	{
		$user = $request->user();

		if ($user && $user->isAdmin())
		{
			return $next($request);
		}
		return new RedirectResponse(url('/'));
	}

}

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évu dans le modèle User que nous avons vu lors du précédent article. 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 :

Route::get('admin', [
	'uses' => 'AdminController@admin',
	'as' => 'admin',
	'middleware' => 'admin'
]);

Mais on le retrouve aussi dans certains contrôleurs comme par exemple 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.

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 :

<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\RedirectResponse;

class IsRedactor {

	/**
	 * Handle an incoming request.
	 *
	 * @param  \Illuminate\Http\Request  $request
	 * @param  \Closure  $next
	 * @return mixed
	 */
	public function handle($request, Closure $next)
	{
		$user = $request->user();

		if ($user && $user->isNotUser())
		{
			return $next($request);
		}
		return new RedirectResponse(url('/'));
	}

}

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 :

Route::get('medias', [
	'uses' => 'AdminController@filemanager',
	'as' => 'medias',
	'middleware' => 'redac'
]);

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 par exemple 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.

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 :

<?php namespace App\Http\Middleware;

use Closure;

class IsAjax {

	public function handle($request, Closure $next)
	{
		if ($request->ajax())
		{
			return $next($request);			
		}

		abort(404);
	}

}

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 :

img33

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 :

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'admin' => \App\Http\Middleware\IsAdmin::class,
    'redac' => \App\Http\Middleware\IsRedactor::class,
    'ajax' => \App\Http\Middleware\IsAjax::class
];

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

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’elle 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 :

img34

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

<?php

namespace App\Jobs;

use App\Jobs\Job;
use Request;

class SetLocale extends Job
{
    /**
     * Execute the command.
     *
     * @return void
     */
    public function handle()
    {
        
        if(!session()->has('locale'))
        {
            session()->put('locale', Request::getPreferredLanguage( config('app.languages') ));
        }

        app()->setLocale(session('locale'));
    }
}

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 :

<?php

namespace App\Jobs;

use App\Jobs\Job;
use Illuminate\Contracts\Bus\SelfHandling;

class ChangeLocale extends Job implements SelfHandling
{
    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        session()->set('locale', $this->lang);
    }
}

Ici on se contente de changer la locale.

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

<?php

namespace App\Http\Middleware;

use Closure;

use App\Jobs\SetLocale;

use Illuminate\Bus\Dispatcher as BusDispatcher;
use App\Events\UserAccess;

class App
{

    /**
     * The command bus.
     *
     * @array $bus
     */
    protected $bus;

    /**
     * The command bus.
     *
     * @array $bus
     */
    protected $setLocale;

    /**
     * Create a new App instance.
     *
     * @param  Illuminate\Bus\Dispatcher $bus
     * @param  App\Jobs\SetLocaleCommand $setLocaleCommand
     * @return void
    */
    public function __construct(
        BusDispatcher $bus,
        SetLocale $setLocale)
    {
        $this->bus = $bus;
        $this->setLocale = $setLocale;
    }

    /**
     * Handle an incoming request.
     *
     * @param  Illuminate\Http\Request  $request
     * @param  Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $this->bus->dispatch($this->setLocale);

        event(new UserAccess);

        return $next($request);
    }

}

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 :

img22

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

/**
 * Change language.
 *
 * @param  App\Jobs\ChangeLocaleCommand $changeLocaleCommand
 * @return Response
 */
public function language(
    ChangeLocale $changeLocale)
{
    ...
    $this->dispatch($changeLocale);
    return redirect()->back();
}

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

J’ai réalisé cette partie avec des commandes, 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 !

Print Friendly, PDF & Email

2 commentaires

  • Saradimi

    Hello Bestmomo.
    Avant tout, merci pour tes précieux tutos qui m’aident beaucoup dans mon apprentissage de Laravel.
    Juste une petite remarque car je me suis bien pris la tête hier avec des bug de soumissions de formulaire sur une app que je développe.
    Je m’était inspiré (entre autre…) de ton fichier de routes que je suis allez prendre directement sur le dépot Github.
    Visiblement, les routes sont par défaut dans le middleware web et le fait de le rajouter dans le fichier créé des dysfonctionnements. Je me suis aperçu avec la commande route:list que pour chaque route, le middleware web apparaissait 2 fois.
    Du coup, pour que mon app fonctionne correctement, j’ai dû supprimer le « Route::group([‘middleware’ => [‘web’]], function () { » qui encadrait toutes les routes.
    Peut-être est-ce dû à une mise à jour du framework ou, plus simplement, ai-je loupé quelque chose mais ce truc m’a pris la tête 2 bonnes heures 🙁

    • bestmomo

      Bonjour,

      L’article n’était plus à jour, j’en ai profité pour l’actualiser, désolé pour le désagrément. Cette application évolue pas mal parce que je reçois régulièrement des PR sur Github. Donc il vaut mieux aller jeter un oeil directement dans les sources. L’histoire du middleware « web » est assez récente et a causé des soucis à pas mal de monde !

      Merci de ton intérêt pour mon blog !

Laisser un commentaire