Gestion de contexte applicatif.
Nous avons vu dans l’article précédent qu’il était possible de rendre plus libre la navigation dans une application web. Mais pour cela il est nécessaire de garder en mémoire les informations en cours de traitement. Mais cela n’est pas suffisant. Il est aussi nécessaire de bien les associer au traitement en cours. En effet il ne suffit pas de mettre les informations d’un client en mémoire car si l’utilisateur entame deux processus qui portent sur deux clients nous risquons d’avoir des problèmes. On voit bien dans un cas qui faut associer chaque client à son traitement. Partant de ce constat au fil du temps j’en suis venu à définir un espace mémoire associé à un processus, que j’ai nommé « contexte ». Un contexte est donc quelque chose d’extrêmement simple. Dans le cas d’une application web, le contexte doit survivre à l’exécution d’un script. La session est là pour cela.
Une utilisation basique de la session.
Lorsque dans une application nous avons besoin de conserver quelque chose en session, nous lui associons un nom et nous l’enregistrons simplement dans la session. La session n’est en fait qu’une Hash-Table, un tableau associatif. Avec le nom, nous sommes capables de retrouver l’information ainsi préservée. C’est simple et efficace. Mais comme dit dans l’introduction il arrive parfois que cela ne soit pas suffisant. On commence généralement par essayer de trouver de nouveaux noms pour les éléments que l’on met dans la session afin de mieux s’y repérer.
Une approche structurée de la session.
Indépendamment de toute action et de tout processus on peut se demander comment organiser la session pour en faciliter la gestion. Pour cela il est pertinent de se demander ce qu’on place dans une session. La première chose qui vient à l’esprit d’un développeur web expérimenté, est l’utilisateur de l’application. C’est d’ailleurs très souvent par ce point qu’un développeur PHP expérimente pour la première fois la session. En essayant de prendre du recul on s’aperçoit que les informations sur l’utilisateur de l’application sont des informations très générales, elle impacte toute l’application. D’autres informations que nous plaçons en session sont quant à elles très spécifiques à l’opération en cours. Par exemple garder les informations sur un objet qu’on propose à l’édition, pour pouvoir gérer les modifications. Dans l’article précédent, je parlais de garder en session un historique des actions de l’utilisateur. Cet historique est de nouveau un élément transverse qui impacte toute l’application.
On peut rapidement voir que nous mettons en session des objets transverses et d’autre spécifiques à un processus. En regardant de loin une session, on peut dire que nous avons un gros tableau associatif. Et en ce plaçant dans une action on voit vite que seule une partite nous intéresse. Cette partie contient tous les objets transverses. Et tous les objets spécifiques à l’action, au processus en cours. Tout le reste n’a pas d’intérêt. Il peut être intéressant de structurer la session pour différencier ces deux types d’éléments. Reste à trouver comment.
Sous tableau associatif.
La première idée généraliste consiste à découper la session en deux parties. Un tableau associatif pour les objets transverses, et un pour les objets spécifiques. Essayons de voir ce que cela nous apporte. Imaginons que pour la partie globale nous ayons défini un tableau « global. » Chaque fois que nous voudrons accéder à un objet global il nous faudra en passer par le tableau « global » pour récupérer l’élément par son nom. Cela ne nous apporte pas grand-chose par rapport à une session gérée de façon basique. C’est tout aussi simple d’utiliser l’objet directement à partir de son nom. Pour la partie globale donc, l’impact n’est pas très probant voire même complexifié pour rien. Pour la partie spécifique, pour retrouver un élément il nous faut là aussi le retrouver par son nom. Et l’apport et là encore non probant. Cette approche ne nous permet pas de différencier un élément spécifique d’un processus de l’élément spécifique d’un autre. Organiser sa session en objets transverses et objets spécifiques n’est pas pertinente. Mais cela nous permet tout de même de voir qu’il n’est pas bien plus compliqué de gérer les éléments de la session dans des tableaux plutôt que directement dans la session elle-même.
Session et processus.
Forts de notre expérience laissons les objets transverses directement dans la session et définissons un tableau associatif par processus lancé par l’utilisateur. Pour les objets globaux l’utilisation reste aussi simple qu’avec un usage basique de la session et pour les objets spécifiques, nous les retrouvons tous dans le tableau associatif relié au processus. Le tri est donc bien effectué. Chaque processus ayant son tableau directement dans la session, les quelques fois où un processus doit passer une information à un autre il peut, soit utiliser l’espace global, soit utiliser le tableau de cet autre processus. Avec un simple tableau il est donc possible de gérer ce contexte propre à un processus.
Contexte et MVC
Dans Zend_Framework, pour la gestion de la session nous trouvons la classe Zend_Session_Namespace qui permet de définir une nouvelle entrée dans la session. De plus la programmation par objet y est très largement généralisée. Ne serait-il pas intéressant plutôt que d’utiliser un tableau associatif pour le contexte d’utiliser un objet ? Tout comme pour la vue à laquelle nous donnons des informations par ajout d’un nouveau membre, ajouter ou lire un élément de contexte par un membre semble séduisant. La différence entre une simple classe et un tableau associatif n’est pas très grande. Mais passer par une classe contexte vas impliquer des contrainte supplémentaire. Si nous plaçons un objet d’une telle classe en session nous devons être sur qu’à l’ouverture de la session celle-ci sera connue où nous allons nous heurter à une erreur PHP. Que pourrait nous apporter une telle classe ? Le contexte est en fait un tas, un espace où on associe une clef à une valeur, on accède à une valeur par sa clef, et où l’on supprime une valeur par sa clef. Bref exactement ce que fait un tableau associatif ou si on veut rester objet une stdClass de PHP. Quelles sont les spécificités d’un contexte ? Ce qui rend particulier un contexte par rapport à un simple tableau est le fait qu’on va le mettre dans la session et l’utiliser dans une action d’un Contrôleur. Nous pourrions mettre ce petit traitement dans le constructeur d’une classe contexte. Nous devrions alors nous assurer de la présence de la classe à l’ouverture de la session et passer au constructeur le contrôleur de l’action courante pour pouvoir faire la bonne association. En utilisant une stdClass, nous nous retrouvons contraints de faire ce travail dans le contrôleur. J’ai pour ma part préféré cette dernière approche.
Le contrôleur et le contexte.
Pour faciliter l’usage j’ai ajouté un membre context à tous mes contrôleurs. Et une méthode permettant de créer le contexte.
-
/* methode contexte */
-
/**
-
* Crée un espace associé à l’action courante dans la session
-
* et l’attache au controller.
-
*
-
*/
-
protected function _createContext($name)
-
{
-
$this->context = new Zend_Session_Namespace($name);
-
}
Ajouter un objet dans le contexte consiste comme pour la vue à ajouter un membre. De même pour l’utiliser. Il faut noter ici le fonctionnement de Zend_Session_Namespace qui vas créer une entrée dans la session avec le nom $name si cette entrée n’existe pas et qui va sinon lire cette entrée et en retourner une référence. _createContext associe donc l’action courante à une entrée de la session et la créé si nécessaire. Il est parfois nécessaire de trouver le contexte d’un processus sans pour autant vouloir l’associer à l’action courante, pour par exemple y placer ou y lire une information. Cela permet une perméabilité des contextes, maitrisée par le contrôleur.
-
/**
-
* trouve l’espace associé à un autre processus dans la session
-
*
-
*/
-
protected function _getContext($name)
-
{
-
return new Zend_Session_Namespace($name);
-
}
Il est aussi nécessaire de pouvoir supprimer un contexte. Le ménage de la session est quelque chose d’important.
-
/**
-
* Supprime un contexte par son nom
-
*
-
*/
-
protected function _deleteContext($name)
-
{
-
if ($name) {
-
$acontext = new Zend_Session_Namespace($name);
-
$acontext->unsetAll();
-
}
-
}
Avec cela nous pouvons utiliser des contextes associés à nos processus. Il suffit dans les actions du processus d’appeler _createContexte avec un nom choisi pour ce processus. Un contexte est donc finalement quelque chose de relativement simple.
Contexte et historique.
Dans l’article précédent, je parlais de la dynamique de l’application et proposais d’utiliser un historique. Était souhaitable de combiner historique et contexte ? La réponse est donnée dans l’article précédent. Lorsqu’on revient dans la pile de l’historique on en profite pour faire du ménage. Si on a combiné la gestion de contexte à celle de l’historique on sera capable de faire le ménage dans l’historique mais aussi dans les contextes. Nous avons utilisé le chemin de l’action pour la repérer dans l’historique. Très souvent ce nom fait l’affaire pour définir un contexte. En l’utilisant on peut simplifier l’utilisation du contexte. Enfin en plaçant une référence d’un contexte dans toutes les entrées d’historique le concernant, on a un contexte associé à tout le processus de façon quasi automatique. Il nous faut donc revoir un peu la création de contexte et de l’historique. Il nous faut aussi une méthode pour récupérer le contexte de l’action précédente pour gérer les enchainements.
-
protected function _openHistory()
-
{
-
true === $this->_useHistory)
-
{
-
$history = new Zend_Session_Namespace(‘History’);
-
$key = $this->makeActionPath();
-
if (!$this->_inHistory($key)) {
-
$history->{$key} = new StdClass();
-
$history->{$key}->path = $key;
-
$history->{$key}->previous = &$history->$previous;
-
} else {
-
}
-
} else {
-
$context = $history->{$key}->context;
-
foreach ($keys as $akey) {
-
if ($akey == $key) {
-
break;
-
} else {
-
)
-
{
-
$this->_deleteContext($history->{$akey}->context);
-
}
-
}
-
}
-
}
-
$this->history = $history->{$key};
-
} else {
-
return null;
-
}
-
}
-
-
/* methode contexte */
-
/**
-
* Créé un espace associé à l’action courante dans la session
-
* et l’attache au controller.
-
*
-
*/
-
protected function _createContext($name = NULL)
-
{
-
if (!$name) $name = $this->makeActionPath();
-
$this->context = new Zend_Session_Namespace($name);
-
if ($this->_useHistory) {
-
$history = new Zend_Session_Namespace(‘History’);
-
#$history->{$this->makeActionPath()} = new StdClass();
-
$this->history->context = $name;
-
}
-
}
-
-
/**
-
* Supprime un contexte par son nom
-
*
-
*/
-
protected function _deleteContext($name)
-
{
-
if ($name) {
-
$acontext = new Zend_Session_Namespace($name);
-
$acontext->unsetAll();
-
}
-
}
-
-
/**
-
* trouve l’espace associé à l’action précédente dans la session
-
* l’associe à l’action courante et l’attache au controller.
-
*
-
*/
-
protected function _linkContext($name = NULL)
-
{
-
if ($this->_useHistory) {
-
if (!$name) {
-
$name = $this->history->previous->context;
-
} else {
-
$name = $this->makeActionPath();
-
}
-
}
-
$this->context = self::_getContext($name);
-
} else {
-
Zend_Loader::loadClass(‘Fast_Controller_Exception’);
-
throw new Fast_Controller_Exception(Fast_Controller_Exception::INVALID_HISTORY);
-
}
-
}
-
-
/**
-
* trouve l’espace associé à un autre processus dans la session
-
*
-
*/
-
protected function _getContext($name = NULL)
-
{
-
if (!$name) {
-
$name = $this->history->previous->context;
-
} else {
-
$name = $this->makeActionPath();
-
}
-
}
-
return new Zend_Session_Namespace($name);
-
}
Ce code est à placer dans une classe commune à tous les contrôleurs. Chez moi elle s’appelle fast_action. Pour utiliser un contexte il suffit de faire appel à _createContext() dans l’action qui débute le processus. Toutes les action du processus font appel à _linkContext() le ménage étant assuré par la gestion de l’historique. J’ai conservé l’argument $name dans mes méthodes de gestion du contexte pour permettre au contrôleur de définir un autre nom que le chemin de l’action.
Conclusion.
Le contexte n’est finalement qu’une façon d’utiliser la session. Il ne permet pas de gérer toutes les possibilités, mais apporte un cadre clair et simple pour gérer les données d’un processus. J’ai développé cette approche il y a quelques années déjà. Et tout cela est le fruit d’un long murissement. De l’avis des utilisateurs de cette approche, elle permet de clarifier la session et d’en maitriser son contenu.
Pour ma part je dirais qu’elle permet surtout au développeur de se pencher explicitement sur la gestion de sa session. Négliger sa session dans une toute petite application, n’est pas très bon. Mais cela devient vite catastrophique dès que l’application prend de l’ampleur.

