| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- EN-Revision: 24249 -->
- <!-- Reviewed: no -->
- <sect1 id="zend.session.advanced_usage">
- <title>Utilisation avancée</title>
- <para>
- Même si les exemples de l'utilisation basique sont une manière parfaitement acceptable
- d'utiliser les sessions dans Zend Framework, il existe de bonnes pratiques à considérer.
- Cette section détaille plus finement le traitement des sessions et présente des utilisations
- plus avancées du composant <classname>Zend_Session</classname>.
- </para>
- <sect2 id="zend.session.advanced_usage.starting_a_session">
- <title>Démarrer une session</title>
- <para>
- Si vous voulez que toutes les requêtes aient une session facilitée avec
- <classname>Zend_Session</classname>, alors démarrez la session dans votre fichier
- d'amorçage :
- </para>
- <example id="zend.session.advanced_usage.starting_a_session.example">
- <title>Démarrer la session globale</title>
- <programlisting language="php"><![CDATA[
- Zend_Session::start();
- ]]></programlisting>
- </example>
- <para>
- En démarrant la session dans votre fichier d'amorçage, vous empêcher la
- possibilité de démarrer votre session après l'envoi d'en-têtes à votre navigateur, ce
- qui entraîne la levée d'une exception, et peut être une page cassée pour les visiteurs
- de votre site. Divers usages avancés nécessitent premièrement
- <methodname>Zend_Session::start()</methodname>. (D'autant plus sur les utilisations
- avancées suivantes.)
- </para>
- <para>
- Il existe quatre manières différentes pour démarrer une session, quand on utilise
- <classname>Zend_Session</classname>. Deux sont mauvaises.
- </para>
- <orderedlist>
- <listitem>
- <para>
- Mauvaise : n'activez pas <ulink
- url="http://www.php.net/manual/fr/ref.session.php#ini.session.auto-start"><code>session.auto_start</code></ulink>
- de PHP. Si vous n'avez pas la possibilité de désactiver ce réglage dans le
- php.ini, ou que vous utilisez mod_php (ou équivalent), et que le réglage est
- déjà activé dans le <code>php.ini</code>, alors ajoutez le code suivant à votre
- fichier <code>.htaccess</code> (habituellement votre dossier de démarrage HTML)
- : <programlisting language="httpd.conf"><![CDATA[
- php_value session.auto_start 0
- ]]></programlisting></para>
- </listitem>
- <listitem>
- <para>
- Mauvaise : n'utilisez pas la fonction <ulink
- url="http://www.php.net/session_start"><methodname>session_start()</methodname></ulink>
- directement. Si vous utilisez directement <methodname>session_start()</methodname>, et que
- vous démarrez en utilisant <classname>Zend_Session_Namespace</classname>, une
- exception sera levée par <methodname>Zend_Session::start()</methodname> ("session
- has already been started"). Si vous appelez <methodname>session_start()</methodname>, après
- avoir utilisé <classname>Zend_Session_Namespace</classname> ou démarré
- explicitement <methodname>Zend_Session::start()</methodname>, une erreur de niveau
- <constant>E_NOTICE</constant> sera générée, et l'appel sera ignoré.
- </para>
- </listitem>
- <listitem>
- <para>
- Correcte : utilisez <methodname>Zend_Session::start()</methodname>. Si vous
- voulez que toutes vos requêtes aient et utilisent les sessions, alors placez
- cette fonction le plus tôt possible et sans condition dans votre fichier
- d'amorçage. Les sessions ont un coût. Si certaines requêtes nécessitent les
- sessions, mais que les autres n'en ont pas besoin, alors :
- </para>
- <itemizedlist mark="opencircle">
- <listitem>
- <para>
- Sans conditions, réglez l'option <code>strict</code> à
- <constant>TRUE</constant> en utilisant
- <methodname>Zend_Session::setOptions()</methodname> dans votre fichier
- d'amorçage.
- </para>
- </listitem>
- <listitem>
- <para>
- Appelez <methodname>Zend_Session::start()</methodname>, uniquement
- pour les requêtes qui nécessitent l'usage des sessions, avant la
- première instanciation d'un objet
- <methodname>Zend_Session_Namespace()</methodname>.
- </para>
- </listitem>
- <listitem>
- <para>
- Utilisez "<code>new Zend_Session_Namespace()</code>" normalement,
- quand nécessaire, mais faites attention que
- <methodname>Zend_Session::start()</methodname> soit appelée
- auparavant.
- </para>
- </listitem>
- </itemizedlist>
- <para>
- L'option <code>strict</code> empêche <code>new
- Zend_Session_Namespace()</code> d'automatiquement démarrer une session en
- utilisant <methodname>Zend_Session::start()</methodname>. Ainsi, cette option aide
- les développeurs d'application Zend Framework universelles à imposer une
- décision de conception afin d'empêcher l'utilisation de sessions pour certaines
- requêtes, puisqu'une erreur sera levée en utilisant cette option et en
- instanciant <classname>Zend_Session_Namespace</classname>, avant un appel
- explicite de <methodname>Zend_Session::start()</methodname>. N'employez pas cette
- option dans le code de la librairie coeur du ZF, car seuls les développeurs
- universels peuvent faire ce choix de design. Les développeurs doivent considérer
- avec précaution l'impact de l'utilisation de
- <methodname>Zend_Session::setOptions()</methodname>, puisque ces options ont un
- effet global, suite à leur correspondance avec les options sous-jacentes pour
- ext/session.
- </para>
- </listitem>
- <listitem>
- <para>
- Correcte : instanciez simplement
- <methodname>Zend_Session_Namespace()</methodname> quand nécessaire, la session <acronym>PHP</acronym>
- sous-jacente sera automatiquement démarrée. Ceci permet un usage extrêmement
- simple qui fonctionne dans la plupart des cas. Cependant, vous êtes responsable
- de vous assurer que le premier <code>new Zend_Session_Namespace()</code>
- intervient <emphasis>avant</emphasis> que toute sortie (par exemple <ulink
- url="http://www.php.net/headers_sent">en-têtes <acronym>HTTP</acronym></ulink>) ait été envoyée par
- <acronym>PHP</acronym> au client, si vous utilisez le réglage par défaut, sessions basées sur les
- cookies (fortement recommandé). Voir
- <link linkend="zend.session.global_session_management.headers_sent">cette section</link> pour plus
- d'informations.
- </para>
- </listitem>
- </orderedlist>
- </sect2>
- <sect2 id="zend.session.advanced_usage.locking">
- <title>Verrouiller les espaces de noms de session</title>
- <para>
- Les espaces de noms de session peuvent être verrouillés, pour éviter tout risque
- d'altération des données dans cet espace. Utilisez <methodname>lock()</methodname> pour attribuer à
- un espace de nommage spécifique le mode lecture seule,<methodname>unLock()</methodname> pour
- attribuer le mode lecture / écriture, et <methodname>isLocked()</methodname> pour tester si un
- espace de nommage a été auparavant verrouillé. Les verrouillages sont transitoires et ne
- persistent pas d'une requête à l'autre. Verrouiller un espace de nommage n'a pas d'effet
- sur les méthodes de réglages des objets stockés dans cet espace, mais empêche
- l'utilisation des méthodes de réglage de l'espace de noms destiné à détruire ou à
- remplacer les objets stockés dans l'espace. De la même manière, verrouiller les
- instances <classname>Zend_Session_Namespace</classname> n'empêche pas l'accès direct à
- l'alias dans tableau de stockage <varname>$_SESSION</varname> (voir <ulink
- url="http://www.php.net/references">PHP references</ulink>).
- </para>
- <example id="zend.session.advanced_usage.locking.example.basic">
- <title>Verrouillage des espaces de noms de session</title>
- <programlisting language="php"><![CDATA[
- $userProfileNamespace =
- new Zend_Session_Namespace('userProfileNamespace');
- // vérrouillons une session en lecture seule
- $userProfileNamespace->lock();
- // dévérrouillage si déjà vérrouillé
- if ($userProfileNamespace->isLocked()) {
- $userProfileNamespace->unLock();
- }
- ]]></programlisting>
- </example>
- </sect2>
- <sect2 id="zend.session.advanced_usage.expiration">
- <title>Expiration d'un espace de noms</title>
- <para>
- Des limites peuvent être affectées à la durée de vie soit des espaces de noms soit
- de clés individuelles dans cet espace. Les cas d'utilisation habituels incluent le
- passage d'une information temporaire entre requêtes, et la diminution de l'exposition à
- un potentiel risque de sécurité par la suppression de l'accès à des informations
- sensibles potentielles à une certaine heure après que l'authentification ait eu lieu.
- L'expiration peut être basée sur les secondes écoulées, ou basées sur le concept de
- "hops", où un "hop" apparaît à chaque requête successive.
- </para>
- <example id="zend.session.advanced_usage.expiration.example">
- <title>Exemple d'expiration</title>
- <programlisting language="php"><![CDATA[
- $s = new Zend_Session_Namespace('expireAll');
- $s->a = 'apple';
- $s->p = 'pear';
- $s->o = 'orange';
- $s->setExpirationSeconds(5, 'a');
- // expire seulement pour la clé "a" dans 5 secondes
- // expiration de tout l'espace de nommage dans 5 "hops"
- $s->setExpirationHops(5);
- $s->setExpirationSeconds(60);
- // L'espace de noms "expireAll" sera marqué "expired"
- // soit à la première requête reçue après 60 secondes,
- // soit dans 5 hops, en fonction de ce qui arrivera en premier.
- ]]></programlisting>
- </example>
- <para>
- Quand vous travaillez avec des données de session expirées dans la requête
- courante, des précautions doivent être prises concernant leur utilisation. Bien que les
- données soient retournées par référence, modifier les données expirées ne les rendra pas
- persistantes dans la requête courante. Dans le but de remettre à zéro leur temps
- d'expiration, transférez les données dans des variables temporaires, utilisez l'espace
- de nommage pour les effacer, et ensuite réaffectez les clés appropriées de
- nouveau.
- </para>
- </sect2>
- <sect2 id="zend.session.advanced_usage.controllers">
- <title>Encapsulation de session et Contrôleurs</title>
- <para>
- Les espaces de noms peuvent aussi être utilisés pour séparer l'accès aux sessions
- par contrôleur afin de protéger les variables d'une quelconque contamination. Par
- exemple, un contrôleur d'authentification pourrait garder ces données de session
- séparées de tous les autres contrôleurs pour des raisons de sécurité.
- </para>
- <example id="zend.session.advanced_usage.controllers.example">
- <title>Sessions nommées par contrôleur avec expiration automatique</title>
- <para>
- Le code suivant, partie d'un contrôleur destiné à afficher une question dans
- un test, initie une variable booléenne pour représenter l'acceptation ou non d'une
- réponse à la question soumise. Dans ce cas, l'utilisateur de l'application a 300
- secondes pour répondre à la question affichée.
- </para>
- <programlisting language="php"><![CDATA[
- $testSpace = new Zend_Session_Namespace('testSpace');
- $testSpace->setExpirationSeconds(300, 'accept_answer');
- // expire seulement cette variable
- $testSpace->accept_answer = true;
- ]]></programlisting>
- <para>
- Ci-dessous, le contrôleur qui analyse les réponses aux questions du test
- détermine l'acceptation ou non d'une réponse en se basant sur le fait que
- l'utilisateur a répondu dans le temps alloué :
- </para>
- <programlisting language="php"><![CDATA[
- // contrôleur analysant la réponse
- $testSpace = new Zend_Session_Namespace('testSpace');
- if ($testSpace->accept_answer === true) {
- // dans le temps autorisé
- }
- else {
- // pas dans le temps autorisé
- }
- ]]></programlisting>
- </example>
- </sect2>
- <sect2 id="zend.session.advanced_usage.single_instance">
- <title>Limiter les instances multiples par espace de noms</title>
- <para>
- Bien que <link linkend="zend.session.advanced_usage.locking">le verrouillage de
- session</link> fournisse un bon degré de protection contre l'utilisation inattendue des
- données dans un espace de noms, <classname>Zend_Session_Namespace</classname> offre
- aussi la possibilité d'empêcher la création d'instances multiples correspondant à un
- unique espace de noms.
- </para>
- <para>
- Pour activer ce comportement, réglez à <constant>TRUE</constant> le second argument du
- constructeur quand vous créez la dernière instance autorisée de
- <classname>Zend_Session_Namespace</classname>. Tout tentative suivante d'instanciation
- du même espace de noms entraînera la levée d'une exception.
- </para>
- <example id="zend.session.advanced_usage.single_instance.example">
- <title>Limiter l'accès à un espace de noms à une instance unique</title>
- <programlisting language="php"><![CDATA[
- // créer une instance d'espace
- $authSpaceAccessor1 = new Zend_Session_Namespace('Zend_Auth');
- // créer une autre instance du même espace,
- // mais désactiver toute nouvelle instance
- $authSpaceAccessor2 = new Zend_Session_Namespace('Zend_Auth', true);
- // créer une référence est toujours possible
- $authSpaceAccessor3 = $authSpaceAccessor2;
- $authSpaceAccessor1->foo = 'bar';
- assert($authSpaceAccessor2->foo, 'bar');
- try {
- $aNamespaceObject = new Zend_Session_Namespace('Zend_Auth');
- } catch (Zend_Session_Exception $e) {
- echo "Cannot instantiate this namespace "
- . "since $authSpaceAccessor2 was created\n";
- }
- ]]></programlisting>
- </example>
- <para>
- Le second paramètre dans le constructeur ci-dessus informe
- <classname>Zend_Session_Namespace</classname> que toute future instance avec l'espace de
- noms "Zend_Auth" sera refusée. Tenter de créer une instance entraînera la levée d'une
- exception par le constructeur. Le développeur devient responsable de stocker quelque
- part une référence à l'instance de l'objet (<varname>$authSpaceAccessor1</varname>,
- <varname>$authSpaceAccessor2</varname>, ou <varname>$authSpaceAccessor3</varname> dans l'exemple
- ci-dessus), si l'accès à l'espace de noms de session est nécessaire plus tard dans la
- même requête. Par exemple, le développeur peut stocker la référence dans une variable
- statique , ajouter la référence au <ulink
- url="http://www.martinfowler.com/eaaCatalog/registry.html">registre</ulink> (voir
- <link linkend="zend.registry">Zend_Registry</link>), ou sinon la rendre disponible pour
- les autres méthodes qui peuvent avoir accès à cet espace de noms.
- </para>
- </sect2>
- <sect2 id="zend.session.advanced_usage.arrays">
- <title>Travailler avec les tableaux</title>
- <para>
- A cause de l'histoire de l'implémentation des méthodes magiques dans <acronym>PHP</acronym>, la
- modification d'un tableau à l'intérieur d'un espace de noms peut ne pas fonctionner avec
- les versions de <acronym>PHP</acronym> inférieures à 5.2.1. Si vous travaillez exclusivement avec des
- versions de <acronym>PHP</acronym> 5.2.1 ou supérieur., alors vous pouvez passer la <link
- linkend="zend.session.advanced_usage.objects">section suivante</link>.
- </para>
- <example id="zend.session.advanced_usage.arrays.example.modifying">
- <title>Modifier un tableau de données avec un espace de noms de session</title>
- <para>Le code suivant illustre le problème qui peut être reproduit :</para>
- <programlisting language="php"><![CDATA[
- $sessionNamespace = new Zend_Session_Namespace();
- $sessionNamespace->array = array();
- $sessionNamespace->array['testKey'] = 1;
- // ne fonctionne pas comme attendu avant PHP 5.2.1
- echo $sessionNamespace->array['testKey'];
- ]]></programlisting>
- </example>
- <example id="zend.session.advanced_usage.arrays.example.building_prior">
- <title>Construire les tableaux avant le stockage en session</title>
- <para>
- Si possible, évitez le problème en stockant les tableaux dans un espace de
- noms de session seulement après que toutes les clés et les valeurs aient été
- définies :
- </para>
- <programlisting language="php"><![CDATA[
- $sessionNamespace = new Zend_Session_Namespace('Foo');
- $sessionNamespace->array = array('a', 'b', 'c');
- ]]></programlisting>
- </example>
- <para>
- Si vous utilisez une version de <acronym>PHP</acronym> affectée et avez besoin de modifier un tableau
- après l'avoir assigné à une clé dans l'espace de noms, vous pouvez utiliser l'une des
- solutions suivantes :
- </para>
- <example id="zend.session.advanced_usage.arrays.example.workaround.reassign">
- <title>Solution : réassigner un tableau modifié</title>
- <para>
- Dans le code suivant, une copie du tableau stocké est créée, modifiée, et
- réassignée à la place d'où provenait la copie, en effaçant le tableau
- original.
- </para>
- <programlisting language="php"><![CDATA[
- $sessionNamespace = new Zend_Session_Namespace();
- // assigne le tableau initial
- $sessionNamespace->array = array('fruit' => 'pomme');
- // copie du tableau
- $tmp = $sessionNamespace->array;
- // modification de la copie
- $tmp['fruit'] = 'poire';
- // ré-assignation de la copie dans l'espace de noms
- $sessionNamespace->array = $tmp;
- echo $sessionNamespace->array['fruit']; // affiche "poire"
- ]]></programlisting>
- </example>
- <example id="zend.session.advanced_usage.arrays.example.workaround.reference">
- <title>Solution : stocker un tableau contenant une référence</title>
- <para>
- Autrement, stockez un tableau contenant une référence au tableau désiré, et y
- accéder indirectement.
- </para>
- <programlisting language="php"><![CDATA[
- $myNamespace = new Zend_Session_Namespace('myNamespace');
- $a = array(1, 2, 3);
- $myNamespace->someArray = array( &$a );
- $a['foo'] = 'bar';
- echo $myNamespace->someArray['foo']; // affiche "bar"
- ]]></programlisting>
- </example>
- </sect2>
- <sect2 id="zend.session.advanced_usage.objects">
- <title>Utiliser les sessions avec des objets</title>
- <para>
- Si vous prévoyez de rendre persistant des objets dans les sessions <acronym>PHP</acronym>, pensez
- qu'ils peuvent être <ulink
- url="http://www.php.net/manual/fr/language.oop.serialization.php">sérialisé</ulink> pour
- le stockage. Ainsi, tout objet persistant dans les sessions <acronym>PHP</acronym> doit être désérialisé
- après sa récupération à partir du stockage. L'implication est que le développeur doit
- s'assurer que les classes des objets persistants doivent avoir été définies avant que
- l'objet ne soit désérialisé du stockage. Si aucune classe n'est définie pour l'objet
- désérialisé, alors il devient une instance de <code>stdClass</code>.
- </para>
- </sect2>
- <sect2 id="zend.session.advanced_usage.testing">
- <title>Utiliser les sessions avec les tests unitaires</title>
- <para>
- Zend Framework s'appuie sur PHPUnit pour faciliter ses propres tests. Beaucoup de
- développeurs étendent la suite des tests unitaires pour couvrir le code de leurs
- applications. L'exception "<emphasis>Zend_Session is currently marked as
- read-only</emphasis>" (NDT. : "Zend_Session est actuellement marquée en lecture seule")
- est levée lors de l'exécution des tests unitaires, si une méthode d'écriture est
- utilisée après la clôture de la session. Cependant les tests unitaires employant
- <classname>Zend_Session</classname> requièrent une attention particulière, car la
- fermeture (<methodname>Zend_Session::writeClose()</methodname>), ou la destruction d'une
- session (<methodname>Zend_Session::destroy()</methodname>) empêche tout futur changement
- ou suppression de clés dans un <classname>Zend_Session_Namespace</classname>. Ce
- comportement est un résultat direct du mécanisme fondamental de l'extension session et
- des fonctions <acronym>PHP</acronym> <methodname>session_destroy()</methodname> et <methodname>session_write_close()</methodname>,
- qui n'a pas de mécanisme de marche arrière ("undo") pour faciliter le réglage/démontage
- avec les tests unitaires.
- </para>
- <para>
- Pour contourner ceci, regardez le test unitaire
- <methodname>testSetExpirationSeconds()</methodname> dans
- <code>tests/Zend/Session/SessionTest.php</code> et <code>SessionTestHelper.php</code>,
- qui utilise le code <acronym>PHP</acronym> <methodname>exec()</methodname> pour charger un processus séparé. Le nouveau
- processus simule plus précisément une seconde requête successive du navigateur. Le
- processus séparé démarre avec une session "propre", comme n'importe quelle exécution de
- <acronym>PHP</acronym> pour une requête Web. Ainsi, tout changement fait à <varname>$_SESSION</varname> dans le
- processus appelant devient disponible dans le processus enfant, pourvu que le parent ait
- fermé la session avant d'utiliser <methodname>exec()</methodname>.
- </para>
- <example id="zend.session.advanced_usage.testing.example">
- <title>Utilisation de PHPUnit pour tester le code écrit avec Zend_Session*</title>
- <programlisting language="php"><![CDATA[
- // tester setExpirationSeconds()
- require 'tests/Zend/Session/SessionTestHelper.php';
- // voir aussi SessionTest.php dans trunk/
- $script = 'SessionTestHelper.php';
- $s = new Zend_Session_Namespace('espace');
- $s->a = 'abricot';
- $s->o = 'orange';
- $s->setExpirationSeconds(5);
- Zend_Session::regenerateId();
- $id = Zend_Session::getId();
- session_write_close();
- // relâche la session donc le processus suivant peut l'utiliser
- sleep(4); // pas assez long pour les éléments expirent
- exec($script . "expireAll $id expireAll", $result);
- $result = $this->sortResult($result);
- $expect = ';a === abricot;o === orange;p === pear';
- $this->assertTrue($result === $expect,
- "iteration over default Zend_Session namespace failed; "
- . "expecting result === '$expect', but got '$result'");
- sleep(2);
- // assez long pour que les éléments expirent
- // (total de 6 secondes écoulées, avec une expiration de 5)
- exec($script . "expireAll $id expireAll", $result);
- $result = array_pop($result);
- $this->assertTrue($result === '',
- "iteration over default Zend_Session namespace failed; "
- . "expecting result === '', but got '$result')");
- session_start(); // redémarre artificiellement une session suspendue
- // Ceci peut être découpé dans un test séparé, mais en réalité,
- // si quoi que ce soit reste de la partie précédente et contamine
- // les tests suivants, alors c'est un bug dont nous voulons avoir
- // des informations
- $s = new Zend_Session_Namespace('expireGuava');
- $s->setExpirationSeconds(5, 'g');
- // maintenant essayons d'expirer seulement une clé dans l'espace
- $s->g = 'guava';
- $s->p = 'peach';
- $s->p = 'plum';
- session_write_close();
- // relâche la session donc le processus suivant peut l'utiliser
- sleep(6); // pas assez long pour les éléments expirent
- exec($script . "expireAll $id expireGuava", $result);
- $result = $this->sortResult($result);
- session_start(); // redémarre artificiellement la session suspendue
- $this->assertTrue($result === ';p === plum',
- "iteration over named Zend_Session namespace failed (result=$result)");
- ]]></programlisting>
- </example>
- </sect2>
- </sect1>
|