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 :

  1. D'un tableau $urls qui contiendra les url auxquelles le client a accédé,
  2. 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 :

  1. Décoder les données sauvegardées dans la session,
  2. Vérifier :
    1. si la requête http est de type GET,
    2. si l'historique n'est pas vide et si sa dernière valeur n'est pas égale à l'url courante,
  3. 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é.