| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- <!-- EN-Revision: 12759 -->
- <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 role="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
- <classname>Zend_Session::start()</classname>. (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 role="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"><code>session_start()</code></ulink> directement. Si vous
- utilisez directement <code>session_start()</code>, et que vous démarrez en utilisant
- <classname>Zend_Session_Namespace</classname>, une exception sera levée par <classname>Zend_Session::start()</classname>
- ("session has already been started"). Si vous appelez <code>session_start()</code>, après avoir utilisé
- <classname>Zend_Session_Namespace</classname> ou démarré explicitement <classname>Zend_Session::start()</classname>, une
- erreur de niveau <code>E_NOTICE</code> sera générée, et l'appel sera ignoré.</para>
- </listitem>
- <listitem>
- <para>Correcte : utilisez <classname>Zend_Session::start()</classname>. 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> à <code>true</code> en utilisant
- <classname>Zend_Session::setOptions()</classname> dans votre fichier d'amorçage.</para>
- </listitem>
- <listitem>
- <para>Appelez <classname>Zend_Session::start()</classname>, uniquement pour les requêtes qui nécessitent
- l'usage des sessions, avant la première instanciation d'un objet
- <classname>Zend_Session_Namespace()</classname>.</para>
- </listitem>
- <listitem>
- <para>Utilisez "<code>new Zend_Session_Namespace()</code>" normalement, quand nécessaire, mais
- faites attention que <classname>Zend_Session::start()</classname> 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 <classname>Zend_Session::start()</classname>. 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
- <classname>Zend_Session::start()</classname>. 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 <classname>Zend_Session::setOptions()</classname>, 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 <classname>Zend_Session_Namespace()</classname> quand nécessaire, la
- session PHP 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 role="strong">avant</emphasis> que toute
- sortie (par exemple <ulink url="http://www.php.net/headers_sent">en-têtes HTTP</ulink>) ait été envoyée
- par PHP au client, si vous utilisez le réglage par défaut, sessions basées sur les cookies (fortement
- recommandé). Voir <xref linkend="zend.session.global_session_management.headers_sent" /> 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 <code>lock()</code> pour attribuer à un espace de nommage spécifique le mode lecture
- seule,<code>unLock()</code> pour attribuer le mode lecture / écriture, et <code>isLocked()</code> 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
- <code>$_SESSION</code> (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 role="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 role="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 role="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 role="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 à <code>true</code> 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 role="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 (<code>$authSpaceAccessor1</code>, <code>$authSpaceAccessor2</code>, ou
- <code>$authSpaceAccessor3</code> 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 <xref
- linkend="zend.registry" />), 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 PHP, la modification d'un tableau à
- l'intérieur d'un espace de noms peut ne pas fonctionner avec les versions de PHP inférieures à 5.2.1. Si vous
- travaillez exclusivement avec des versions de PHP 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 role="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 role="php"><![CDATA[
- $sessionNamespace = new Zend_Session_Namespace('Foo');
- $sessionNamespace->array = array('a', 'b', 'c');
- ]]></programlisting>
- </example>
- <para>Si vous utilisez une version de PHP 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 role="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 role="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 PHP, 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 PHP 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
- role="strong">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 (<classname>Zend_Session::writeClose()</classname>), ou la
- destruction d'une session (<classname>Zend_Session::destroy()</classname>) 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 PHP <code>session_destroy()</code> et
- <code>session_write_close()</code>, 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 <code>testSetExpirationSeconds()</code> dans
- <code>tests/Zend/Session/SessionTest.php</code> et <code>SessionTestHelper.php</code>, qui utilise le code PHP
- <code>exec()</code> 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 PHP pour une requête Web. Ainsi, tout changement fait à <code>$_SESSION</code> dans le processus
- appelant devient disponible dans le processus enfant, pourvu que le parent ait fermé la session avant d'utiliser
- <code>exec()</code>.</para>
- <example id="zend.session.advanced_usage.testing.example">
- <title>Utilisation de PHPUnit pour tester le code écrit avec Zend_Session*</title>
- <programlisting role="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>
|