Bien que ZF3 vous fournisse de nombreux types de routes, dans certaines situations, vous devrez écrire votre propre type de route.
Un exemple du besoin d'un type de route personnalisé est lorsque vous devez définir des règles de mappage d'URL dynamiquement. Habituellement, vous stockez la configuration de routage dans le fichier de configuration du module, mais dans certains systèmes CMS, vous aurez des documents stockés dans la base de données. Pour un tel système, vous devez développer un type d'itinéraire personnalisé qui se connecte à la base de données et effectue une correspondance d'itinéraire avec les données stockées dans la base de données. Vous ne pouvez pas stocker ces informations dans le fichier de configuration, car les nouveaux documents sont créés par les administrateurs système et non par des développeurs.
Nous savons que chaque classe de route doit implémenter l'interface Zend\Router\Http\RouteInterface
.
Les méthodes de cette interface sont présentées dans le tableau 5.4 :
Nom de la méthode | Description |
---|---|
factory($options) |
Méthode statique pour la création de la classe de routage. |
match($request) |
Méthode qui effectue une correspondance avec les données de requête HTTP. |
assemble($params, $options) |
Méthode de génération d'URL par paramètres de route. |
getAssembledParams() |
Méthode permettant de récupérer les paramètres utilisés pour la génération d'URL. |
La méthode statique factory()
est utilisée par le routeur ZF3 (TreeRouteStack
ou SimpleRouteStack
) pour instancier la classe de routage. Le routeur
passe le tableau options
en argument pour la méthode factory()
.
La méthode match()
est utilisée pour effectuer la mise en correspondance de la requête HTTP (plus particulierement
de son URL) avec les données d'options transmises à la classe route via factory()
. La méthode match()
doit retourner une instance de la classe RouteMatch
en cas de correspondance réussie ou null
en cas d'échec.
La méthode assemble()
est utilisée pour générer une URL par paramètres et options de route.
Le but de l'aide de vue getAssembledParams()
est de renvoyer le tableau de paramètres qui a été utilisé
pour la génération de l'URL.
Pour illustrer la création d'un type de route personnalisé, améliorons notre approche précédente de
création du système de documentation simple avec le type de route Regex. L'inconvénient du type de route
Regex est que vous ne pouvez pas organiser les pages statiques dans une hiérarchie en créant des
sous-répertoires sous le répertoire doc (lorsque vous générez une URL pour une telle page, le séparateur
barre oblique ('/') sera encodé et l'URL inutlisable).
Nous allons donc créer une classe StaticRoute
personnalisée qui permet de résoudre ce problème.
La classe que nous allons créer sera plus puissante car elle ne reconnaîtra pas seulement les URL commençant par "/doc" et se terminant par ".html". Eelle reconnaîtra aussi les URL génériques, comme "/help" ou "/support/chapter1/introduction".
Ce que nous voulons :
La classe StaticRoute
doit être insérable dans la pile de routage (SimpleRouteStack
ou TreeRouteStack
)
et utilisable avec d'autres types de routage.
La classe de route doit reconnaître les URL génériques, comme "/help" ou "/introduction".
La classe de route doit faire correspondre l'URL à la structure du répertoire. Par exemple, si l'URL est "/chapter1/introduction", l'itinéraire doit vérifier si le fichier de vue correspondant <base_dir>/chapter1/introduction.phtml existe et est lisible, et si c'est le cas, signaler la correspondance. Si le fichier n'existe pas (ou n'est pas lisible), renvoyez un statut d'échec.
La classe de route doit vérifier que les URL ont des noms de fichiers acceptables en utilisant une expression régulière. Par exemple, le nom de fichier "introduction" est acceptable, mais le nom "*int$roduction" ne l'est pas. Si le nom de fichier n'est pas acceptable, le statut d'échec doit être renvoyé.
La route devrait être capable d'assembler l'URL par son nom et ses paramètres.
Pour commencer, créez le sous-répertoire Route dans le répertoire source du module et placez-y le fichier StaticRoute.php (figure 5.9).
Dans ce fichier, collez le bout de code présenté ci-dessous :
<?php
namespace Application\Route;
use Traversable;
use \Zend\Router\Exception;
use \Zend\Stdlib\ArrayUtils;
use \Zend\Stdlib\RequestInterface as Request;
use \Zend\Router\Http\RouteInterface;
use \Zend\Router\Http\RouteMatch;
// Route personnalisée qui déssert les pages web "statiques".
class StaticRoute implements RouteInterface
{
// Crée une nouvelle route avec des options données.
public static function factory($options = [])
{
}
// Correspond à une requête donnée.
public function match(Request $request, $pathOffset = null)
{
}
// Assemble une URL par ses paramètres de route.
public function assemble(array $params = [], array $options = [])
{
}
// Récupère la liste des paramètres utilisés lors de l'assemblage.
public function getAssembledParams()
{
}
}
Dans l'exemple ci-dessus, vous pouvez voir que nous avons placé la classe StaticRoute
dans l'espace de
noms Application\Route
(ligne 2).
Aux lignes 4-9, nous définissons des alias de nom de classe pour raccourcir leurs noms.
Avec lignes 12-33, nous définissons le code pour la classe StaticRoute
. La classe StaticRoute
implémente l'interface RouteInterface
et définit toutes les méthodes
spécifiées par l'interface: factory()
, match()
, assemble()
et getAssembledParams()
.
Ensuite, ajoutons plusieurs propriétés protégées et la méthode constructeur de la classe StaticRoute
,
comme indiqué ci-dessous :
<?php
//...
class StaticRoute implements RouteInterface
{
// Répertoire de base des vues.
protected $dirName;
// Préfixe du chemin vers les vues.
protected $templatePrefix;
// Modèle de nom de fichier.
protected $fileNamePattern = '/[a-zA-Z0-9_\-]+/';
// Valeurs par défaut
protected $defaults;
// Liste des paramètres assemblés.
protected $assembledParams = [];
// Constructeur.
public function __construct($dirName, $templatePrefix,
$fileNamePattern, array $defaults = [])
{
$this->dirName = $dirName;
$this->templatePrefix = $templatePrefix;
$this->fileNamePattern = $fileNamePattern;
$this->defaults = $defaults;
}
// ...
}
Ci-dessus, à la ligne 7, nous définissons la propriété $dirName
qui est destinée à stocker le nom du
répertoire de base où seront situés les vues "statiques". A la ligne 10, nous définissons la variable de
classe $templatePrefix
pour stocker le préfixe à ajouter à tous les noms de vues.
La ligne 13 contient la variable $fileNamePattern
qui sera utilisée pour vérifier le nom du fichier.
Lignes 22-29, nous définissons la méthode constructeur appelée lors de la création de l'instance pour initialiser les propriétés protégées.
Implémentons ensuite la méthode factory()
pour notre classe de route personnalisée StaticRoute
.
La méthode factory()
sera appelée par le routeur pour instancier la classe route :
<?php
//...
class StaticRoute implements RouteInterface
{
//...
// Crée une nouvelle route avec les options données
public static function factory($options = [])
{
if ($options instanceof Traversable) {
$options = ArrayUtils::iteratorToArray($options);
} elseif (!is_array($options)) {
throw new Exception\InvalidArgumentException(__METHOD__ .
' expects an array or Traversable set of options');
}
if (!isset($options['dir_name'])) {
throw new Exception\InvalidArgumentException(
'Missing "dir_name" in options array');
}
if (!isset($options['template_prefix'])) {
throw new Exception\InvalidArgumentException(
'Missing "template_prefix" in options array');
}
if (!isset($options['filename_pattern'])) {
throw new Exception\InvalidArgumentException(
'Missing "filename_pattern" in options array');
}
if (!isset($options['defaults'])) {
$options['defaults'] = [];
}
return new static(
$options['dir_name'],
$options['template_prefix'],
$options['filename_pattern'],
$options['defaults']);
}
}
Dans le code ci-dessus, nous voyons que la méthode factory()
prend le tableau options
comme argument
(ligne 9). Le tableau options
peut contenir les options de configuration de la classe de route.
La classe StaticRoute
acceptera les options suivantes:
dir_name
- le répertoire de base où stocker tous les vues "statiques".template_prefix
- le préfixe à ajouter à tous les noms de vues.filename_pattern
- l'expression régulière pour vérifier les noms des fichiers.defaults
- les paramètres renvoyés par défaut par le routeur.Une fois que nous avons analysé les options, dans les lignes 37-41, nous appelons le constructeur
de la classe pour instancier et retourner l'objet StaticRoute
.
La méthode suivante que nous ajoutons à la classe StaticRoute
est la méthode match()
:
<?php
//...
class StaticRoute implements RouteInterface
{
//...
// Vérifie la correspondance avec une requête donnée.
public function match(Request $request, $pathOffset=null)
{
// S'assure que ce type de route est utilisé dans une requête HTTP
if (!method_exists($request, 'getUri')) {
return null;
}
// Récupère l'URL et sa partie chemin.
$uri = $request->getUri();
$path = $uri->getPath();
if($pathOffset!=null)
$path = substr($path, $pathOffset);
// Récupère le tableau des segments de chemin.
$segments = explode('/', $path);
// Vérifie chaque segment par rapport au modèle des noms de fichier autorisé.
foreach ($segments as $segment) {
if(strlen($segment)==0)
continue;
if(!preg_match($this->fileNamePattern, $segment))
return null;
}
// Vérifie si le fichier .phtml existe sur le disque
$fileName = $this->dirName . '/'.
$this->templatePrefix.$path.'.phtml';
if(!is_file($fileName) || !is_readable($fileName)) {
return null;
}
$matchedLength = strlen($path);
// Prépare l'objet RouteMatch
return new RouteMatch(array_merge(
$this->defaults,
['page'=>$this->templatePrefix.$path]
),
$matchedLength);
}
}
Dans le code ci-dessus, nous voyons que la méthode match()
prend deux arguments : l'objet de requête HTTP
(une instance de Zend\Stdlib\Request
) et le paramètre offset du chemin d'URL.
L'objet de requête est utilisé pour accéder à la requete URL (ligne 17).
Le paramètre offset du chemin est un entier non négatif, qui pointe vers la partie de l'URL avec laquelle
la route est comparée (ligne 21).
À la ligne 24, nous extrayons les segments de l'URL. Ensuite, nous vérifions si chaque segment est un nom de fichier (répertoire) acceptable (lignes 27-32). Si le segment n'est pas un nom de fichier valide, nous renvoyons null comme code d'échec.
A la ligne 35, nous calculons le chemin vers la vue, et aux lignes 37-39 nous vérifions si un tel fichier existe réellement et est accessible à la lecture. De cette façon, nous faisons correspondre l'URL à la structure du répertoire.
Aux lignes 44-48, nous préparons et renvoyons l'objet RouteMatch
avec les
paramètres par défaut et le paramètre "page" contenant le nom de la vue pour le rendu.
Pour compléter l'implémentation de notre classe StaticRoute
, nous ajoutons les méthodes assemble()
et getAssembledParams()
qui seront utilisées pour la génération d'URL par les paramètres de route.
Le code de ces méthodes est présenté ci-dessous :
<?php
//...
class StaticRoute implements RouteInterface
{
//...
// AAssemble une URL par des paramètres de route
public function assemble(array $params = [],
array $options = [])
{
$mergedParams = array_merge($this->defaults, $params);
$this->assembledParams = [];
if(!isset($params['page'])) {
throw new Exception\InvalidArgumentException(__METHOD__ .
' expects the "page" parameter');
}
$segments = explode('/', $params['page']);
$url = '';
foreach($segments as $segment) {
if(strlen($segment)==0)
continue;
$url .= '/' . rawurlencode($segment);
}
$this->assembledParams[] = 'page';
return $url;
}
// Récupère la liste des paramètres utilisés lors de l'assemblage.
public function getAssembledParams()
{
return $this->assembledParams;
}
}
Dans le code ci-dessus, nous définissons la méthode assemble()
, qui prend deux arguments : le tableau
parameters
et le tableau options
(ligne 9). La méthode construit l'URL en encodant chaque segments et
en les concaténant (ligne 20-26).
La méthode getAssembledParams()
renvoie simplement les noms des paramètres que nous avons utilisés pour
générer l'URL (ligne 36).
Nous avons maintenant terminé notre classe StaticRoute
. Pour utiliser notre type de route personnalisé,
nous ajoutons la configuration suivante au fichier de configuration module.config.php :
'static' => [
'type' => StaticRoute::class,
'options' => [
'dir_name' => __DIR__ . '/../view',
'template_prefix' => 'application/index/static',
'filename_pattern' => '/[a-z0-9_\-]+/',
'defaults' => [
'controller' => Controller\IndexController::class,
'action' => 'static',
],
],
],
A la ligne 1 de la configuration ci-dessus, nous déclarons la règle de routage "static".
Le paramètre type définit le nom complet de la classe StaticRoute
(ligne 2). Dans le tableau options
,
nous définissons le répertoire de base où seront placées les pages "statiques" (ligne 4), le préfixe des
vues (ligne 5), le pattern du nom de fichier (ligne 6) et le tableau defaults
contenant le nom du
contrôleur et l'action qui servira toutes les pages statiques.
N'oubliez pas d'insérer la ligne suivante au début de la classe
module.config.php
:
use Application\Route\StaticRoute;
La dernière étape consiste à créer la méthode d'action corresponndante dans la classe IndexController
:
public function staticAction()
{
// On récupère le chemin d'accès aux vues à partir des paramètres de route
$pageTemplate = $this->params()->fromRoute('page', null);
if($pageTemplate==null) {
$this->getResponse()->setStatusCode(404);
return;
}
// On effectue le rendu de la page
$viewModel = new ViewModel([
'page'=>$pageTemplate
]);
$viewModel->setTemplate($pageTemplate);
return $viewModel;
}
L'action ci-dessus est presque identique à l'action que nous avons utilisée pour la route Regex.
À la ligne 4, nous récupérons le paramètre de route page
et l'enregistrons en tant que variable
$pageTemplate
. À la ligne 11, nous créons le conteneur de variables ViewModel
, et à la ligne 14,
nous définissons explicitement le nom de la vue pour le rendu.
Pour voir le système en action, ajoutons quelques pages d'affichage "statiques" : la page d'aide
(help.phtml
) et la page introduction (intro.phtml
).
Créez le sous-dossier static sous le répertoire view/application/index du module Application
et placez la vue help.phtml :
<h1>Aide</h1>
<p>
Voir l'aide <a href="<?= $this->url('static',
['page'=>'/chapter1/intro']); ?>">introduction</a> here.
</p>
Créez ensuite le sous-dossier chapter1 dans le répertoire static et placez le fichier chapter1/intro.phtml suivant:
<h1>Introduction</h1>
<p>
Écrivez l'introduction de vos pages d'aide ici.
</p>
Au final, vous devriez avoir la structure de dossiers suivante (voir figure 5.10) :
Éventuellement, ouvrez l'URL suivante dans votre navigateur : http://localhost/help. La page d'aide devrait apparaître (voir la figure 5.11 en exemple). Si vous tapez l'URL http://localhost/chapter1/intro dans votre navigateur, vous devriez voir la page Introduction (figure 5.12).
Vous pouvez créer des pages statiques en ajoutant simplement les fichiers phtml dans le répertoire static et ils seront automatiquement disponibles pour les utilisateurs du site.
Si vous êtes bloqué, vous pouvez trouver cet exemple au complet dans l'application Hello World.