1 <?xml version="1.0" encoding="UTF-8"?>
3 <!-- EN-Revision: 21740 -->
4 <sect1 id="zend.session.advanced_usage">
9 基本的な使用法の例で Zend Framework のセッションを完全に使用できますが、
10 よりよい方法もあります。ここでは、セッションの処理方法や
11 <classname>Zend_Session</classname> コンポーネントのより行動な使用法を説明します。
14 <sect2 id="zend.session.advanced_usage.starting_a_session">
16 <title>セッションの開始</title>
19 すべてのリクエストで <classname>Zend_Session</classname> の機能を使用してセッション管理したい場合は、
23 <example id="zend.session.advanced_usage.starting_a_session.example">
25 <title>グローバルセッションの開始</title>
27 <programlisting language="php"><![CDATA[
28 Zend_Session::start();
35 ヘッダがブラウザに送信される前に確実にセッションが始まるようにします。
36 そうしないと例外が発生してしまい、おそらくユーザが見るページは崩れてしまうでしょう。
37 さまざまな高度な機能を使用するには、まず <methodname>Zend_Session::start()</methodname>
38 が必要です (高度な機能の詳細については後で説明します)。
42 <classname>Zend_Session</classname> を使用してセッションを開始する方法は四通りありますが、
49 間違い: <acronym>PHP</acronym> の
50 <ulink url="http://www.php.net/manual/ja/ref.session.php#ini.session.auto-start"><code>session.auto_start</code>
51 </ulink> を有効にしてはいけません。
52 もし mod_php (やそれと同等のもの) を使用しており、
53 <code>php.ini</code> でこの設定が有効になっている、かつそれを無効にすることができない
54 という場合は、<code>.htaccess</code> ファイル (通常は HTML のドキュメントルートにあります)
56 <programlisting language="httpd.conf"><![CDATA[
57 php_value session.auto_start 0
63 間違い: <acronym>PHP</acronym> の
64 <ulink url="http://www.php.net/session_start"><methodname>session_start()</methodname></ulink>
66 <methodname>session_start()</methodname> を直接使用した後で <classname>Zend_Session_Namespace</classname> を使用した場合は、
67 <methodname>Zend_Session::start()</methodname> が例外 ("session has already been started")
68 をスローします。<classname>Zend_Session_Namespace</classname> を使用するか
69 明示的に <methodname>Zend_Session::start()</methodname> で開始した後で
70 <methodname>session_start()</methodname> をコールすると、<code>E_NOTICE</code>
76 正解: <methodname>Zend_Session::start()</methodname> を使用します。
77 すべてのリクエストでセッションを使用したい場合は、
78 この関数コールを起動コードの最初のほうで無条件に記述します。
79 セッションにはある程度のオーバーヘッドがあります。
80 セッションを使用したいリクエストとそうでないリクエストがある場合は、
82 <itemizedlist mark="opencircle">
85 起動コード内で、<methodname>Zend_Session::setOptions()</methodname> を使用して
86 無条件にオプション <code>strict</code> を <constant>TRUE</constant> にします。
92 <classname>Zend_Session_Namespace</classname> のインスタンスを作成する前に
93 <methodname>Zend_Session::start()</methodname> をコールします。
98 通常どおり、必要に応じて "<code>new Zend_Session_Namespace()</code>"
99 を使用します。事前に <methodname>Zend_Session::start()</methodname>
100 がコールされていることを確認しておきましょう。
105 <code>strict</code> オプションにより、<code>new Zend_Session_Namespace()</code>
106 が自動的に <methodname>Zend_Session::start()</methodname> でセッションを開始することがなくなります。
107 したがって、このオプションを使用すると、アプリケーションの開発者が
108 特定のリクエストにはセッションを使用しないという設計をおこなうことができます。
110 <methodname>Zend_Session::start()</methodname> をコールする前に Zend_Session_Namespace
111 のインスタンスを作成しようとしたときに例外がスローされます。
112 開発者は、<methodname>Zend_Session::setOptions()</methodname>
113 の使用がユーザにどれだけの影響を与えるかを注意するようにしましょう。
115 (もととなる ext/session のオプションと同様)、
121 正解: 必要に応じて <classname>Zend_Session_Namespace</classname> のインスタンスを作成します。
122 <acronym>PHP</acronym> のセッションは、自動的に開始されます。
123 これはもっともシンプルな使用法で、たいていの場合にうまく動作します。
124 しかし、デフォルトであるクッキーベースのセッション (強く推奨します)
125 を使用している場合には、<acronym>PHP</acronym> がクライアントに何らかの出力
126 (<ulink url="http://www.php.net/headers_sent">HTTP ヘッダ</ulink> など)
127 をする <emphasis>前に</emphasis>、確実に
128 最初の <code>new Zend_Session_Namespace()</code> をコールしなければなりません。
129 詳細は <xref linkend="zend.session.global_session_management.headers_sent" />
137 <sect2 id="zend.session.advanced_usage.locking">
139 <title>セッション名前空間のロック</title>
143 それ以降その名前空間のデータに手を加えられないようにできます。
145 <methodname>lock()</methodname> を、そして
146 読み取り専用の名前空間を読み書きできるようにするには <methodname>unLock()</methodname>
147 を使用します。<methodname>isLocked()</methodname> を使用すると、
148 その名前空間がロックされているかどうかを調べることができます。
149 このロックは一時的なものであり、そのリクエスト内でのみ有効となります。
150 名前空間をロックしても、その名前空間に保存されているオブジェクトの
151 セッターメソッドには何の影響も及ぼしません。
152 しかし、名前空間自体のセッターメソッドは使用できず、
153 名前空間に直接格納されたオブジェクトの削除や置換ができなくなります。同様に、
154 <classname>Zend_Session_Namespace</classname> のインスタンスをロックしたとしても、
155 同じデータをさすシンボルテーブルの使用をとめることはできません
156 (<ulink url="http://www.php.net/references"><acronym>PHP</acronym>
157 のリファレンスについての説明</ulink>も参照ください)。
160 <example id="zend.session.advanced_usage.locking.example.basic">
162 <title>セッション名前空間のロック</title>
164 <programlisting language="php"><![CDATA[
165 $userProfileNamespace = new Zend_Session_Namespace('userProfileNamespace');
167 // このセッションに読み取り専用ロックをかけます
168 $userProfileNamespace->lock();
171 if ($userProfileNamespace->isLocked()) {
172 $userProfileNamespace->unLock();
180 <sect2 id="zend.session.advanced_usage.expiration">
182 <title>名前空間の有効期限</title>
185 名前空間およびその中の個々のキーについて、その寿命を制限できます。
186 これは、たとえばリクエスト間で一時的な情報を渡す際に使用します。
187 これにより、認証内容などの機密情報へアクセスできないようにし、
188 セキュリティリスクを下げます。有効期限の設定は経過秒数によって決めることもできますし、
189 "ホップ" 数によって決めることもできます。ホップ数とは、
193 <example id="zend.session.advanced_usage.expiration.example">
195 <title>有効期限切れの例</title>
197 <programlisting language="php"><![CDATA[
198 $s = new Zend_Session_Namespace('expireAll');
203 $s->setExpirationSeconds(5, 'a'); // キー "a" だけは 5 秒で有効期限切れとなります
205 // 名前空間全体は、5 "ホップ" で有効期限切れとなります
206 $s->setExpirationHops(5);
208 $s->setExpirationSeconds(60);
209 // "expireAll" 名前空間は、60 秒が経過するか
210 // 5 ホップに達するかのどちらかが発生した時点で
217 現在のリクエストで期限切れになったデータを扱うにあたり、
219 データは参照で返されますが、それを変更したとしても
220 期限切れのデータを現在のリクエストから持ち越すことはできません。
221 有効期限を "リセット" するには、取得したデータをいったん一時変数に格納し、
222 名前空間上の内容を削除し、あらためて適切なキーで再設定します。
227 <sect2 id="zend.session.advanced_usage.controllers">
229 <title>コントローラでのセッションのカプセル化</title>
232 名前空間を使用すると、コントローラによるセッションへのアクセスの際に
234 たとえば、認証コントローラでは、セキュリティの観点から
235 そのセッション状態データを他のコントローラとは別に管理することになるでしょう。
238 <example id="zend.session.advanced_usage.controllers.example">
240 <title>コントローラでの名前空間つきセッションによる有効期限の管理</title>
243 次のコードは、質問を表示するコントローラの一部です。
244 ここでは論理型の変数を用意して、質問に対する回答を受け付けるかどうかを表しています。
245 この場合は、表示されている質問に 300 秒以内に答えることになります。
248 <programlisting language="php"><![CDATA[
251 $testSpace = new Zend_Session_Namespace('testSpace');
253 $testSpace->setExpirationSeconds(300, 'accept_answer');
254 $testSpace->accept_answer = true;
259 次に、回答を処理するコントローラを示します。
260 時間内に回答したかどうかをもとにして、回答を受け付けるかどうかを判断しています。
263 <programlisting language="php"><![CDATA[
266 $testSpace = new Zend_Session_Namespace('testSpace');
267 if ($testSpace->accept_answer === true) {
279 <sect2 id="zend.session.advanced_usage.single_instance">
281 <title>名前空間内あたりのインスタンス数をひとつに絞り込む</title>
284 <link linkend="zend.session.advanced_usage.locking">セッションのロック</link>
285 を利用すれば、名前空間つきセッションデータを予期せず使用してしまうことはある程度防げます。
286 しかし、<classname>Zend_Session_Namespace</classname> には、
287 単一の名前空間内で複数のインスタンスを作成することを防ぐ機能もあります。
291 この機能を有効にするには、<classname>Zend_Session_Namespace</classname>
292 のインスタンスを作成する際に、コンストラクタの第二引数に <constant>TRUE</constant>
293 を渡します。それ以降は、同一名前空間でインスタンスを作成しようとすると例外がスローされます。
296 <example id="zend.session.advanced_usage.single_instance.example">
298 <title>セッション名前空間へのアクセスを単一のインスタンスに制限する</title>
300 <programlisting language="php"><![CDATA[
302 $authSpaceAccessor1 = new Zend_Session_Namespace('Zend_Auth');
304 // 同じ名前空間で別のインスタンスを作成します。
305 // しかし今後はインスタンスを作成できないようにします
306 $authSpaceAccessor2 = new Zend_Session_Namespace('Zend_Auth', true);
309 $authSpaceAccessor3 = $authSpaceAccessor2;
311 $authSpaceAccessor1->foo = 'bar';
313 assert($authSpaceAccessor2->foo, 'bar');
316 $aNamespaceObject = new Zend_Session_Namespace('Zend_Auth');
317 } catch (Zend_Session_Exception $e) {
318 echo 'この名前空間ではインスタンスを作成できません。すでに ' .
319 '$authSpaceAccessor2 があるからです\n';
326 上の例では、コンストラクタの第二引数を用いて
327 "<classname>Zend_Auth</classname>" 名前空間では今後インスタンスを作成させないよう
328 <classname>Zend_Session_Namespace</classname> に指示しています。
329 インスタンスを作成しようとすると、コンストラクタから例外がスローされます。
330 したがって、このセッション名前空間へのアクセスが必要となった場合は、
331 今後は現在あるインスタンス (上の例の場合なら <code>$authSpaceAccessor1</code>、
332 <code>$authSpaceAccessor2</code> あるいは <code>$authSpaceAccessor3</code>)
334 たとえば、名前空間への参照を静的変数に格納したり、
335 <ulink url="http://www.martinfowler.com/eaaCatalog/registry.html">レジストリ</ulink>
336 (<xref linkend="zend.registry" /> を参照ください) に格納したり、
337 あるいは名前空間へのアクセスを必要とするその他のメソッドで使用したりします。
342 <sect2 id="zend.session.advanced_usage.arrays">
347 <acronym>PHP</acronym> のマジックメソッドの実装上の理由で、バージョン 5.2.1 より前の <acronym>PHP</acronym>
349 もし <acronym>PHP</acronym> 5.2.1 以降を使っている場合は、<link
350 linkend="zend.session.advanced_usage.objects">このセクションは読み飛ばしてください</link>。
353 <example id="zend.session.advanced_usage.arrays.example.modifying">
355 <title>セッション名前空間内での配列データの修正</title>
361 <programlisting language="php"><![CDATA[
362 $sessionNamespace = new Zend_Session_Namespace();
363 $sessionNamespace->array = array();
365 // PHP 5.2.1 より前のバージョンでは、期待通りに動作しません
366 $sessionNamespace->array['testKey'] = 1;
367 echo $sessionNamespace->array['testKey'];
372 <example id="zend.session.advanced_usage.arrays.example.building_prior">
374 <title>セッションに保存する前に配列を作成する</title>
377 可能なら、先に配列のすべての値を設定してからセッションに格納するようにすればこの問題を回避できます。
380 <programlisting language="php"><![CDATA[
381 $sessionNamespace = new Zend_Session_Namespace('Foo');
382 $sessionNamespace->array = array('a', 'b', 'c');
388 この問題の影響を受けるバージョンの <acronym>PHP</acronym> を使っている場合で、
389 セッション名前空間に代入した後に配列を修正したい場合は、
390 以下の回避策のうちのいずれかを使用します。
393 <example id="zend.session.advanced_usage.arrays.example.workaround.reassign">
395 <title>回避策: 修正した配列を再度代入する</title>
398 以下のコードでは、保存されている配列のコピーを作成してそれを修正し、
399 修正したコピーを再度代入してもとの配列を上書きします。
402 <programlisting language="php"><![CDATA[
403 $sessionNamespace = new Zend_Session_Namespace();
406 $sessionNamespace->array = array('tree' => 'apple');
409 $tmp = $sessionNamespace->array;
412 $tmp['fruit'] = 'peach';
414 // 修正したコピーをセッション名前空間に書き戻します
415 $sessionNamespace->array = $tmp;
417 echo $sessionNamespace->array['fruit']; // prints "peach"
422 <example id="zend.session.advanced_usage.arrays.example.workaround.reference">
424 <title>回避策: 参照を含む配列を格納する</title>
427 あるいは、実際の配列への参照を含む配列を格納しておき、
431 <programlisting language="php"><![CDATA[
432 $myNamespace = new Zend_Session_Namespace('myNamespace');
434 $myNamespace->someArray = array( &$a );
436 echo $myNamespace->someArray['foo']; // "bar" と表示されます
443 <sect2 id="zend.session.advanced_usage.objects">
445 <title>セッションでのオブジェクトの使用</title>
448 オブジェクトを <acronym>PHP</acronym> セッション内で持続的に使用したい場合は、
449 <ulink url="http://www.php.net/manual/ja/language.oop.serialization.php">シリアライズ</ulink>
450 を使用します。したがって、<acronym>PHP</acronym> セッションから永続オブジェクトを取得したら、
451 そのシリアライズを解除しなければなりません。
452 ということは、永続オブジェクトをセッションから読み出す前に、
453 そのオブジェクトのクラスが定義されていなければならないということです。
454 クラスが定義されていない場合は、<code>stdClass</code>
460 <sect2 id="zend.session.advanced_usage.testing">
462 <title>ユニットテストでのセッションの使用</title>
465 Zend Framework 自体のテストには PHPUnit を使用しています。
466 多くの開発者は、このテストスイートを拡張して自分のアプリケーションのコードをテストしています。
467 ユニットテスト中で、セッションの終了後に書き込み関連のメソッドを使用すると
468 "<emphasis>Zend_Session is currently marked as read-only</emphasis>"
469 という例外がスローされます。しかし、<classname>Zend_Session</classname> を使用するユニットテストには要注意です。
470 セッションを閉じたり (<methodname>Zend_Session::writeClose()</methodname>)
471 破棄したり (<methodname>Zend_Session::destroy()</methodname>) したら、
472 それ以降は <classname>Zend_Session_Namespace</classname> のインスタンスへのキーの設定や削除ができなくなります。
473 これは、ext/session や、<acronym>PHP</acronym> の
474 <methodname>session_destroy()</methodname> および <methodname>session_write_close()</methodname>
475 の仕様によるものです, これらには、ユニットテストの setup/teardown
476 時に使用できるような、いわゆる "undo" 機能が備わっていないのです。
481 <code>SessionTest.php</code> および <code>SessionTestHelper.php</code>
482 (どちらも <code>tests/Zend/Session</code> にあります)
483 のユニットテストテスト <methodname>testSetExpirationSeconds()</methodname> を参照ください。
484 これは、<acronym>PHP</acronym> の <methodname>exec()</methodname> によって別プロセスを起動しています。
485 新しいプロセスが、ブラウザからの二番目以降のリクエストをシミュレートします。
486 この別プロセスの開始時にはセッションを "初期化" します。
487 ちょうど、ふつうの <acronym>PHP</acronym> スクリプトがウェブリクエストを実行する場合と同じような動作です。
488 また、呼び出し元のプロセスで <code>$_SESSION</code> を変更すると、
490 <methodname>exec()</methodname> を使用する前にセッションを閉じています。
493 <example id="zend.session.advanced_usage.testing.example">
495 <title>PHPUnit で Zend_Session を使用したコードをテストする例</title>
497 <programlisting language="php"><![CDATA[
498 // testing setExpirationSeconds()
499 $script = 'SessionTestHelper.php';
500 $s = new Zend_Session_Namespace('space');
503 $s->setExpirationSeconds(5);
505 Zend_Session::regenerateId();
506 $id = Zend_Session::getId();
507 session_write_close(); // release session so process below can use it
508 sleep(4); // not long enough for things to expire
509 exec($script . "expireAll $id expireAll", $result);
510 $result = $this->sortResult($result);
511 $expect = ';a === apple;o === orange;p === pear';
512 $this->assertTrue($result === $expect,
513 "iteration over default Zend_Session namespace failed; " .
514 "expecting result === '$expect', but got '$result'");
516 sleep(2); // long enough for things to expire (total of 6 seconds
517 // waiting, but expires in 5)
518 exec($script . "expireAll $id expireAll", $result);
519 $result = array_pop($result);
520 $this->assertTrue($result === '',
521 "iteration over default Zend_Session namespace failed; " .
522 "expecting result === '', but got '$result')");
523 session_start(); // resume artificially suspended session
525 // We could split this into a separate test, but actually, if anything
526 // leftover from above contaminates the tests below, that is also a
527 // bug that we want to know about.
528 $s = new Zend_Session_Namespace('expireGuava');
529 $s->setExpirationSeconds(5, 'g'); // now try to expire only 1 of the
530 // keys in the namespace
535 session_write_close(); // release session so process below can use it
536 sleep(6); // not long enough for things to expire
537 exec($script . "expireAll $id expireGuava", $result);
538 $result = $this->sortResult($result);
539 session_start(); // resume artificially suspended session
540 $this->assertTrue($result === ';p === plum',
541 "iteration over named Zend_Session namespace failed (result=$result)");