XXX. Chapitre 30 : Le filtrage▲
On a vu dans le chapitre précédent qu'on peut contraindre les paramètres d'une route, mais qu'on est rapidement assez limité. Si on désire quelque chose de plus complet et efficace, on a à notre disposition le filtrage. Les filtres sont des règles d'action que l'on applique sur les routes, en amont (before) ou en aval (after). Ils se situent physiquement dans le dossier app/filters.php :
XXX-A. Les filtres par défaut▲
Par défaut on y trouve déjà les filtres before, after, auth, auth.basic, guest et csrf :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
App::
before(function($request
)
{
//
}
);
App::
after(function($request
,
$response
)
{
//
}
);
Route::
filter('
auth
'
,
function()
{
if (Auth::
guest()) return Redirect::
guest('
login
'
);
}
);
Route::
filter('
auth.basic
'
,
function()
{
return Auth::
basic();
}
);
Route::
filter('
guest
'
,
function()
{
if (Auth::
check()) return Redirect::
to('
/
'
);
}
);
Route::
filter('
csrf
'
,
function()
{
if (Session::
token() !=
Input::
get('
_token
'
))
{
throw new Illuminate\Session\TokenMismatchException;
}
}
);
XXX-A-1. Les filtres globaux▲
Les filtres before et after sont particuliers parce qu'ils s'appliquent à toutes les routes. Autrement dit le filtrage que vous effectuez à ce niveau concernera tous les URL qui arrivent. Imaginez par exemple que vous voulez appliquer le filtre CSRF à toutes les requêtes de type « post ». Vous pouvez évidemment le prévoir dans vos routes ou vos contrôleurs, mais vous pouvez aussi anticiper dans le filtre before, par exemple ainsi :
De la même façon vous pouvez intervenir sur la réponse avec le filtre after. Supposons que vous voulez ajouter quelque chose systématiquement, vous pouvez le faire facilement :
2.
3.
4.
5.
6.
App::
after(
function ($request
,
$response
) {
$content
=
$response
->
getContent() .
'
<br>Mon petit ajout
'
;
$response
->
setContent($content
);
}
);
Maintenant avec la route :
et l'URL http://localhost/laravel/public/ vous obtenez :
Coucou
Mon petit ajout
À l'usage on utilise essentiellement les filtres before. En effet il est intéressant d'effectuer un tri avant la gestion des routes, la faire après présente bien moins d'intérêt et ne correspond qu'à des cas très particuliers.
XXX-B. Les filtres d'authentification▲
Il y a trois filtres prévus de base pour l'authentification :
- auth pour vérifier si l'utilisateur est authentifié ;
- auth.basic pour vérifier l'authentification « basique » qui utilise l'email;
- guest qui est l'inverse de auth.
Pour utiliser ces filtres sur une route la syntaxe est la suivante :
Ici on applique le filtre auth sur la route en effectuant ce filtrage avant d'accéder à la route (before), donc si l'utilisateur n'est pas authentifié il n'aura pas accès aux messages. Si vous regardez l'URL généré dans ce cas vous trouvez http://localhost/laravel/public/login. Pourquoi ? Parce que dans le filtre auth on a une redirection en cas d'échec :
Il faut donc gérer cette redirection dans le cadre d'une application, comme je l'ai montré dans ce chapitre chapitrechapitreChapitre 15 : Les bases de données 3/3. Vous pouvez évidemment modifier la cible si « login » ne vous plaît pas. Ppour tester l'efficacité du filtrage dans sa version positive vous pouvez authentifier artificiellement un utilisateur :
Il faut évidemment avoir une table active dans votre base de données et le modèle correspondant. Et cette fois vous devez obtenir :
Je peux lire les messages !
Si vous voulez activer une route, non plus pour les utilisateurs authentifiés mais pour les autres, alors il faut utiliser le filtre guest :
Si vous voulez tester tout ça pensez à supprimer les cookies entre deux tests .
Le filtre auth.basic s'utilise exactement de la même façon. Il est basé sur la vérification de l'email par défaut mais vous pouvez changer ça avec ce code :
Auth::
basic('
username
'
);
Maintenant c'est le nom de l'utilisateur qui va servir de référence, et on obtient une fenêtre de connexion :
XXX-C. Le filtre CSRF▲
Ce filtre sert à se protéger contre les attaques de type CSRF. Il est appliqué systématiquement pour les formulaires, mais vous devez préciser que vous l'utilisez au niveau de la requête de retour. Il y a plusieurs façons de localiser ce filtre. On a déjà vu ci-dessus une façon de l'appliquer systématiquement à toutes les requêtes « post » qui arrivent dans le filtre global before. Ce n'est pas toujours un comportement judicieux. On peut l'appliquer directement à une route :
Si vous avez plusieurs routes qui nécessitent ce filtre vous pouvez les grouper :
Une autre façon de faire est de prévoir le filtrage au niveau d'un contrôleur :
2.
3.
4.
public function __construct()
{
$this
->
beforeFilter('
csrf
'
,
array('
on
'
=>
'
post
'
));
}
Si vous le faites dans le BaseController il concernera évidemment tous les contrôleurs !
XXX-D. Créer un filtre▲
On peut aussi ajouter ses propres filtres dans ce fichier. C'est ce que nous allons faire maintenant. Créer un filtre est tout simple. Admettons que l'on veuille limiter des noms à une liste, voilà le filtre :
On utilise la méthode filter pour créer un filtre. Comme les filtres vus précédemment, il a un nom, ici noms. Il a également une fonction anonyme pour le traitement. Ici on a un tableau avec les noms autorisés. On teste que le nom est dans le tableau et, si ce n'est pas le cas, on redirige vers la page d'accueil. Par contre si c'est le cas on traite la route de façon classique en continuant l'exécution normale. Voici les routes :
La syntaxe pour intégrer un filtre à une route est simple comme vous pouvez le voir. Au niveau du fonctionnement on a :
- http://localhost/laravel/public/ renvoie « Accueil » ;
- http://localhost/laravel/public/Gaston renvoie « Salut Gaston » ;
- http://localhost/laravel/public/Paul renvoie « Accueil ».
Vous pouvez utiliser after au lieu de before à ce niveau, mais alors ce n'est plus vraiment un filtre de route et je n'en vois pas trop l'utilité .
XXX-E. Plusieurs filtres▲
On peut appliquer autant de filtres que l'on veut. Par exemple complétons le cas précédent en prévoyant aussi un âge limite :
On applique les deux filtres à la route :
On a mis les noms des deux filtres séparés par le signe « | ». Les filtres sont exécutés l'un après l'autre en commençant par la gauche. Cet ordre d'exécution peut avoir une incidence dans des cas particuliers, mais n'est en général pas important comme dans notre exemple. Le fonctionnement est le suivant :
- http://localhost/laravel/public/ renvoie « Accueil » ;
- http://localhost/laravel/public/Gaston/25 renvoie « Salut Gaston, vous avez 25 ans » ;
- http://localhost/laravel/public/Gaston/5 renvoie « Accueil » ;
- http://localhost/laravel/public/Paul/50 renvoie « Accueil ».
Rien ne vous empêche de coupler un filtre « before » avec un filtre « after ». La syntaxe est alors la suivante :
XXX-F. Paramètres dans les filtres▲
XXX-F-1. Un paramètre▲
Il peut être utile de transmettre des paramètres à un filtre. Supposons que notre filtre pour l'âge doive être utilisé plusieurs fois avec des âges limites différents. À ce moment ce serait bien que la route envoie cette limite au filtre. Voici notre exemple modifié :
Et voici le filtre :
Remarquez les paramètres du filtre : $route qui est la route actuelle, $request qui est la requête en cours, puis notre paramètre. Voyons si ça fonctionne :
- http://localhost/laravel/public/ renvoie « Accueil » ;
- http://localhost/laravel/public/cas1/25 renvoie « Salut vous avez 25 ans » ;
- http://localhost/laravel/public/cas1/5 renvoie « Accueil » ;
- http://localhost/laravel/public/cas2/45 renvoie « Salut vous avez 45 ans » ;
- http://localhost/laravel/public/cas2/25 renvoie « Accueil ».
Aurait-on la même syntaxe pour un filtre « after » ? Pas tout à fait parce qu'un filtre « after » est appliqué après traitement de la route, on a donc généré une réponse et il est sans doute utile de disposer de cette réponse dans le code du filtre. C'est pour cette raison que dans le cas d'un filtre « after » on a un paramètre supplémentaire pour pouvoir agir sur la réponse :
2.
3.
4.
Route::
filter('
ages
'
,
function($route
,
$request
,
$response
,
$mon_paramètre
)
{
....
}
);
XXX-F-2. Plusieurs paramètres▲
Complétons notre exemple avec un deuxième paramètre :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
Route::
get('
membres
'
,
function()
{
return '
Accueil des membres
'
;
}
);
Route::
get('
actifs
'
,
function()
{
return '
Accueil des actifs
'
;
}
);
Route::
get('
cas1/{age}
'
,
array('
before
'
=>
'
ages:membres,20
'
,
function($age
)
{
return "
Salut vous avez
$age
ans
"
;
}
));
Route::
get('
cas2/{age}
'
,
array('
before
'
=>
'
ages:actifs,40
'
,
function($age
)
{
return "
Salut vous avez
$age
ans
"
;
}
));
Et voici le filtre :
- http://localhost/laravel/public/cas1/25 renvoie « Salut vous avez 25 ans » ;
- http://localhost/laravel/public/cas1/5 renvoie « Accueil des membres » ;
- http://localhost/laravel/public/cas2/45 renvoie « Salut vous avez 45 ans » ;
- http://localhost/laravel/public/cas2/25 renvoie « Accueil des actifs ».
XXX-G. Un patron de route▲
Il peut arriver que vous deviez faire agir un filtre sur plusieurs routes qui ont la même structure d'URL. Par exemple imaginez une gestion d'articles avec tous les URL qui commencent par « articles/ ». Et vous désirez que ces URL ne soient accessibles que pour les utilisateurs authentifiés. Vous pouvez alors utiliser cette syntaxe :
Et voici un exemple rudimentaire pour le filtre :
Si vous voulez authentifier de façon artificielle un utilisateur pour tester ce genre de code, vous pouvez utiliser ce code :
Mais ça ne fonctionnera que si vous avez une base renseignée et accessible .
XXX-H. Les classes de filtres▲
Mettre le code d'un filtre dans une fonction anonyme est une stratégie efficace et en général largement suffisante. Mais il se peut que cela ne vous suffise pas. Par principe une fonction anonyme est localisée, c'est même son principal intérêt, mais elle peut se retourner contre vous selon vos besoins. Si vous voulez une approche plus souple alors vous pouvez créer une classe pour votre filtre et instancier cette classe où et quand vous voulez.
Ajouter une classe à Laravel implique d'informer L'IoC sinon elle restera inconnue et donc inutilisable. Pour gérer convenablement votre code il faut bien la classer et la localiser. Si vous avez des classes de filtres, autant créer un dossier pour mettre ces classes, par exemple app/filters. Vous devez alors informer Composer :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
"autoload"
:
{
"classmap"
:
[
"app/filters"
,
"app/commands"
,
"app/controllers"
,
"app/models"
,
"app/database/migrations"
,
"app/database/seeds"
,
"app/tests/TestCase.php"
]
},
Et évidemment faire un dumpautoload. Vous avez ainsi un endroit judicieux pour placer vos classes de filtres.
Reprenons notre exemple de filtre paramétré sur l'âge vu précédemment, et créons une classe pour ce filtre (app/filters/AgeFilter.php) :
2.
3.
4.
5.
6.
7.
class AgeFilter
{
public
function
filter($route
,
$request
,
$age
)
{
if
(Route::
input('age'
) <
$age
) return
'Vous êtes trop jeune !'
;
}
}
Il faut ensuite agir sur la route :
Mis à part l'organisation du code, tout le reste est conforme à ce que nous avons vu pour les fonctions anonymes.
XXX-I. Filtres sur les contrôleurs▲
Pour le moment nous avons vu les filtres appliqués sur les routes, voyons à présent comment faire avec des contrôleurs.
XXX-I-1. Filtre déclaré dans la route▲
On peut évidemment encore spécifier le filtre au niveau d'une route. Voilà un contrôleur :
2.
3.
4.
5.
class TestController extends
BaseController {
public
function
index($nom
) {
return
"Bonjour
$nom
"
;
}
}
On reprend le filtre pour les noms vu précédemment :
On spécifie le filtre au niveau de la route :
On a le fonctionnement :
- http://localhost/laravel/public/Gaston renvoie « Bonjour Gaston » ;
- http://localhost/laravel/public/Paul renvoie « Accueil » si on a prévu la route pour l'accueil.
XXX-I-2. Filtre déclaré dans le contrôleur▲
Plutôt que dans la route, c'est plus élégant de spécifier le filtre dans le contrôleur :
2.
3.
4.
5.
6.
7.
8.
class TestController extends
BaseController {
public
function
__construct
() {
$this
->
beforeFilter('noms'
);
}
public
function
index($nom
) {
return
"Bonjour
$nom
"
;
}
}
À ce moment-là la route se contente de pointer la méthode du contrôleur :
Tout fonctionne bien mais… si on déclare ainsi ce filtre il sera appliqué à toutes les méthodes du contrôleur. Ce n'est pas toujours ce qu'on désire. Heureusement, on a la possibilité de spécifier les méthodes pour lesquelles le filtre doit agir. Complétons notre contrôleur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
class TestController extends
BaseController {
public
function
__construct
() {
$this
->
beforeFilter('noms'
,
array
('only'
=>
'index'
));
}
public
function
index($nom
) {
return
"Bonjour
$nom
"
;
}
public
function
login($nom
) {
return
"Login de
$nom
"
;
}
public
function
logout($nom
) {
return
"Logout de
$nom
"
;
}
}
J'ai ajouté deux méthodes, et aussi complété la déclaration du filtre, pour cibler uniquement la méthode index. Voici les routes associées :
Route::
get('
index/{nom}
'
,
array('
uses
'
=>
'
testController@index
'
));
Route::
get('
login/{nom}
'
,
array('
uses
'
=>
'
testController@login
'
));
Route::
get('
logout/{nom}
'
,
array('
uses
'
=>
'
testController@logout
'
));
Et le fonctionnement :
- http://localhost/laravel/public/index/Gaston renvoie « Bonjour Gaston » ;
- http://localhost/laravel/public/index/Paul renvoie « Accueil » ;
- http://localhost/laravel/public/login/Paul renvoie « Login de Paul » ;
- http://localhost/laravel/public/logout/Paul renvoie « Logout de Paul ».
On se rend compte que le filtre n'est effectivement appliqué qu'à la méthode index. On peut coder avec la logique inverse en excluant les méthodes non concernées :
À vous de choisir .
XXX-I-3. Filtre dans le contrôleur▲
Vous pouvez aussi mettre le code du filtre directement dans le contrôleur :
C'est une autre façon d'organiser son code si le filtre ne concerne effectivement que ce contrôleur. Ici le code est placé dans le constructeur. Vous pouvez également le mettre dans une méthode du contrôleur :
Il suffit de mettre le nom de la méthode précédé du signe « @ » comme premier paramètre de la méthode beforeFilter.
XXX-I-4. Filtres paramétrés▲
On peut maintenant se poser une question : comment transmettre un paramètre à un filtre déclaré ou codé dans un contrôleur ? Prenons encore notre filtre pour l'âge :
Ce filtre attend un paramètre comme limite d'âge. Au niveau de la route on appelle de façon classique la méthode du contrôleur :
Et dans le contrôleur on déclare le filtre avec son paramètre :
Tout cela est simple mais imaginez maintenant que vous avez un contrôleur de ressources, par exemple pour des livres que vous référencez ainsi au niveau de la route :
Route::
resource('
livre
'
,
'
LivreController
'
);
Vous avez dans le contrôleur une méthode show($id). Si vous voulez filtrer cet ID comment faire ? Vous pouvez récupérer le paramètre dans le filtre avec la méthode getParameter :
Le codage dans le contrôleur est classique :
Je pense que mon tour d'horizon du filtrage est relativement complet, si vous voyez des lacunes dites-le moi .