Ainsi, je n'ai plus besoin de connaître, lors du développement, l'url courante de mon visiteur pour pouvoir le rediriger puisque je n'ai plus qu'à faire appel aux méthodes de l'historique pour cela.
La structure de l'objet est loin d'être complexe puisque nous avons besoin :
- D'un tableau
$urls
qui contiendra les url auxquelles le client a accédé, - D'un entier
$size
qui permettra de ne conserver que les$size
dernières url,
Il devra de plus implémenter l'interface \serializable
pour pouvoir s'interfacer avec le mécanisme de session de PHP.
Son code ressemble donc pour le moment à ceci :
<?php
namespace ogo\http;
class history implements \serializable
{
protected $urls = array();
protected $size = 6;
public function __construct($size = 6)
{
$this->size = $size;
}
public function serialize()
{
}
public function unserialize($serialized)
{
}
}
?>
Il reste donc a définir le comportement des methodes serialize()
et unserialize()
qui seront appelées magiquement
par PHP lorsqu'une instance de la classe sera respectivement sauvegardée en session et extraite de la session.
Le code de la méthode serialize()
est très simple, puisque cette dernière doit retourner au moteur de PHP la chaîne de caractères qui sera sauvegardée en session.
Cette chaîne se génère très facilement en passant en argument à la fonction serialize()
un tableau contenant les propriétés de l'objet qui doivent être sauvegardées en session à la fin d'une requête http :
...
public function serialize()
{
return serialize(array($this->urls, $this->size));
}
...
Le code de la méthode unserialize()
est un peu plus complexe puisqu'elle doit gérer l'ajout de l'url courante dans l'historique.
Pour ce faire, il faut :
- Décoder les données sauvegardées dans la session,
- Vérifier :
- si la requête http est de type
GET
, - si l'historique n'est pas vide et si sa dernière valeur n'est pas égale à l'url courante,
- si la requête http est de type
- Si c'est le cas, ajouter l'url courante à la fin de
$urls
et supprimer la première valeur de$urls
si sa taille dépasse la valeur de$size
.
Son code est donc le suivant :
...
public function unserialize($serialized)
{
$unserializedHistory = unserialize($serialized);
if ($unserializedHistory !== false)
{
$this->urls = $unserializedHistory[0];
$this->size = $unserializedHistory[1];
if ($_SERVER['REQUEST_METHOD'] === 'GET')
{
$currentHttpUrl = \ogo\url\http::getCurrentUrl();
if (sizeof($this->urls) <= 0 || $currentHttpUrl != $this->getLastUrl())
{
$this->urls[] = $currentHttpUrl;
if (sizeof($this->urls) > $this->size)
{
array_shift($this->urls);
}
}
}
}
}
public function getLastUrl()
{
$url = end($this->urls);
return $url['value'];
}
...
Il peut de plus être intéressant de récupèrer l'avant-dernière url, à l'aide de la méthode suivate :
...
public function getPreviousUrl()
{
end($this->urls);
$url = prev($this->urls);
return $url['value'];
}
...
Enfin, il peut être intéressant de pouvoir parcourir l'historique à l'aide de l'instruction foreach
et de pouvoir connaître la taille de l'historique à l'aide de sizeof
.
Pour ce faire, il suffit de rajouter à notre classe les interfaces \countable
et \iteratorAggregate
et de lui ajouter les deux méthodes suivantes :
...
public function getIterator()
{
return new arrayIterator($this->urls);
}
public function count()
{
return sizeof($this->urls);
}
...
Enfin, il faut, pour pouvoir utiliser cette classe dans notre code, en initialiser une instance de la manière suivante dans un fichier qui sera systèmatiquement inclus lors de chaque requête http :
$session = new \ogo\session\php();
if (isset($session->httpHistory) === false)
{
$session->httpHistory = new \ogo\http\history();
}
$httpHistory = $session->httpHistory;
Il n'y a plus qu'à utiliser l'historique dans notre code de départ, sa gestion étant devenue totalement transparente pour le développeur qui peut alors se concentrer sur le code métier
:
<?php
$form = new \ogo\form\post();
if ($form->wasSubmited())
{
switch (true)
{
case $form->insert:
// on insére
header('Status: 303');
header('Location: ' . $httpHistory->getPreviousUrl());
break;
default:
header('Location: ' . $httpHistory->getPreviousUrl());
}
}
else
{
// on affiche le formulaire
}
?>
Evidement, il s'agit là d'un exemple très basique, et l'utilisation de notre classe prend beaucoup plus de sens lorsque, par exemple, nous ajoutons à ce code la gestion des erreurs survenue lors de la création d'un enregistrement dans la base de données et qu'on le combine à un objet capable de manipuler efficacement les en-têtes http.
Cependant, je vous laisse découvrir toutes les applications possibles par vous-même, peut être qu'ainsi vous pourrez me suggérer des utilisations auxquelles je n'ai pas pensé.
Par ailleurs, je m'excuse de ne pas avoir inclus de code propre
dans ce billet, mais une implémentation de ce concept est en cours d'inclusion dans ogo
et elle sera fonctionnelle dans quelque temps, une fois le nettoyage et le remaniement en cours terminé.
6 réactions
1 De Mandar - 18/02/2010, 13:13
Bonjour,
N'y a-t-il pas un souci avec cette méthode si l'utilisateur navigue sur différentes pages du site en parallèle avec des onglets ? Il risque d'être redirigé vers la dernière page visitée sur l'onglet B lors d'une erreur sur l'onglet A. Il me semble que HTTP_REFERER ne pose pas ce problème.
2 De mageekguy - 18/02/2010, 13:24
@Mandar:
Je ne peux pas te répondre concernant HTTP_REFERER, cette variable étant inconnue de mon répertoire personnel pour raison de sécurité et posant au final plus de problèmes qu'elle ne permet d'en résoudre, ne serait-ce que parce que le client peut ne pas envoyer de HTTP_REFERER.
par contre, effectivement, si le visiteur utilise plusieurs onglets, pour un onglet, l'historique ne contiendra pas forcément la dernière url qui aura été accédé via cet onglet mais peut être une url accédée via un autre onglet.
Mais j'ai envie de dire que de toute façon, ce n'est pas bien grave, les urls étant valides dans tout les cas et le but de la classe étant tout de même parfaitement atteint (permettre la redirection de l'utilisateur vers une autre url), même si l'expérience utilisateur peut s'en trouver altérer.
De plus, en général, j'utilise ce mécanisme lorsque l'utilisateur tente de faire des choses "interdites", comme modifier un formulaire, attaquer le site en forgeant des requêtes http ou altérer un identifiant dans l'url (l'exemple du billet a pour but d'illustrer le concept et ne devrait pas être pris au pied de la lettre).
Qu'il se retrouve face à un comportement imprévu dans le cas ou il a plusieurs onglets d'ouvert n'est alors dans ce cas qu'un juste retour des choses.
Et j'ajouterai qu'en cas d'erreur, j'ai pris le plie de rediriger systèmatiquement vers l'url courante.
Ainsi, une requête GET sur une url déclenche l'affichage du formulaire et des erreurs éventuelles résultat du POST, et une requête POST sur cette même url déclenche une action (modification de la base, etc) avec redirection http vers une autre url s'il n'y a pas d'erreur, ou vers la même url dans le cas contraire.
3 De Sebastien - 18/02/2010, 17:00
Ne serait-il pas préférable de ne garder que les URL pertinentes et de passer celles-ci en entête HTTP ?
Ce n'est pas forcément la dernière page visitées qui est la plus pertinente pour une redirection et puis QUID du mec qui demande une URI (qui utilise une redirection) sans historique de navigation ?
Pour généraliser je dirais que ce principe n'est pas très RESTful, même si l'historique est retransmis dans le header, c'est plus un détournement du principe qu'une réelle solution.
4 De mageekguy - 18/02/2010, 17:26
@Sebastien :
L'implémentation "officielle" dispose d'une url de fallback pour gérer le cas ou l'historique est vide.
Et effectivement, suivant l'arborescence et/ou le fonctionnement de l'application, ce n'est pas forcément la dernière url qui est la plus pertinente pour une redirection, mais c'est le cas pour le code que je développe.
Et j'ajouterais que j'ai toujours obligatoirement une url dans l'historique, vu le fonctionnement de mon code.
Et encore une fois, l'exemple du billet a pour but d'illustrer, je dispose d'outils tiers pour manipuler l'url courante et en faire ce que je veux, comme par exemple remonter au dossier supérieur.
Pour le reste (passage dans le header, rest, etc), j'avoue ne pas tout avoir compris, donc un peu d'explication supplémentaire ne serait pas un mal.
5 De epommate - 19/02/2010, 09:16
Bonjour,
Je n'ai pas compris comment tu gères tes formulaires, la page précédent le traitement du formulaire, n'est ce pas l'affichage du formulaire ?
(C'est quoi ogo ?)
6 De mageekguy - 19/02/2010, 10:36
@epommate :
ogo est mon framework personnel.
Et ma gestion de formulaire est la suivante :
En effet, mes urls sont en régle générale de la forme :