高级用法虽然在基本用法中讲到的利用Zend框架的会话管理的方法是完全可以接受的,但还有些最佳实践需要去考虑。这一节讨论会话处理和示例Zend_Session架构的更高级用法的精彩细节。 开启会话如何你希望所有的请求都有个由Zend_Seesion管理的会话,那么请在程序的引导文件中开启它: Example #1 开启全局会话 <?php require_once 'Zend/Session.php'; Zend_Session::start();
在程序的引导文件中开启会话,可以避免引发会话开启之前已经有HTTP头发向用户浏览器的异常,那样可能会破坏web页面的美观。许多高级的特性需要先执行 使用Zend_Session组件,有4种开启会话的方法,其中2种是错误的。
锁住会话命名空间
会话的命名空间可以加锁,以防止意外的变更该命名空间下的会话变量值。使用 Example #2 锁住会话命名空间 <?php require_once 'Zend/Session/Namespace.php'; $userProfileNamespace = new Zend_Session_Namespace('userProfileNamespace'); // 标记会话设置为只读锁定 $userProfileNamespace->lock(); // 解锁只读锁定 if ($userProfileNamespace->isLocked()) { $userProfileNamespace->unLock(); } 命名空间过期对于命名空间和在命名空间中的独立键,它们的寿命都是有限的。在授权后,普通用例包括在请求之间传递临时信息,和通过除去访问潜在的敏感信息来降低一定的安全风险的暴露时有发生。过期可以基于消逝的秒数或者跳步(hop)的个数,对每次初始化命名空间的成功请求,跳步至少发生一次。 Example #3 过期的例子 <?php require_once 'Zend/Session/Namespace.php'; $s = new Zend_Session_Namespace('expireAll'); $s->a = 'apple'; $s->p = 'pear'; $s->o = 'orange'; $s->setExpirationSeconds(5, 'a'); // expire only the key "a" in 5 seconds // 5 次访问后,会话过期 $s->setExpirationHops(5); $s->setExpirationSeconds(60); // 命名空间 "expireAll" 将在第一次访问后 60 秒,或者访问 5 次后过期。 在处理在当前请求中会话数据过期,需要小心来提取它们(会话数据)。尽管数据通过引用返回,修改数据将不使过期数据持续传递当前请求。为了“重置”过期时间,把数据放到临时变量,用命名空间来unset它们,然后再设置合适的键。 会话封装和控制器命名空间可以被用来分离控制器对会话的访问,以免被污染。例如, 一个认证控制器可以为会议安全请求保持它的会话状态数据与其他控制器分离。 Example #4 带有生命期的控制器命名空间会话 下面的代码,作为显示一个测试问题的控制器的一部分,初始化一个布尔变量来表示是否一个提交的答案应该被接受。在此例中,给用户300秒时间来回答所显示的问题。 <?php // ... // in the question view controller require_once 'Zend/Session/Namespace.php'; $testSpace = new Zend_Session_Namespace('testSpace'); $testSpace->setExpirationSeconds(300, 'accept_answer'); // expire only this variable $testSpace->accept_answer = true; //... 下面,处理测试问题答案的控制器根据用户是否在允许的时间内提交答案来决定是否接受答案: <?php // ... // in the answer processing controller require_once 'Zend/Session/Namespace.php'; $testSpace = new Zend_Session_Namespace('testSpace'); if ($testSpace->accept_answer === true) { // within time } else { // not within time } // ... 防止每个命名空间有多重实例
尽管session locking提供了很好的保护来防止意外的命名空间的会话数据的使用,
为开启这个动作,当创建 Example #5 限制命名空间访问单一实例 <?php require_once 'Zend/Session/Namespace.php'; // create an instance of a namespace $authSpaceAccessor1 = new Zend_Session_Namespace('Zend_Auth'); // create another instance of the same namespace, but disallow any new instances $authSpaceAccessor2 = new Zend_Session_Namespace('Zend_Auth', true); // making a reference is still 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"; }
上面构造函数的第二个参数告诉 使用数组在PHP 5.2.1 版本之前,因为PHP魔术方法实现的历史,修改在命名空间里的数组是不可以的。如果你只使用PHP 5.2.1 或以后的版本,那么你可以跳到下一章节。 Example #6 修改带有会话命名空间的数组数据 下面来示例问题如何被复制: <?php require_once 'Zend/Session/Namespace.php'; $sessionNamespace = new Zend_Session_Namespace(); $sessionNamespace->array = array(); $sessionNamespace->array['testKey'] = 1; // may not work as expected before PHP 5.2.1 echo $sessionNamespace->array['testKey']; Example #7 在会话存储之前构造数组 如果可能,通过只在所有期望的数组值被设置后存储数组到一个会话命名空间来完全避免问题的发生。 <?php require_once 'Zend/Session/Namespace.php'; $sessionNamespace = new Zend_Session_Namespace('Foo'); $sessionNamespace->array = array('a', 'b', 'c'); 如果你正使用有影响的PHP版本并需要在分配给一个会话命名空间的键之后修改数组,你可以用下面的其中之一或者全部的方案。 Example #8 方案:重新分配一个被修改的数组 在下面的代码中,创建、修改了一个被存储的数组的拷贝,并且重新从被创建的拷贝分配位置、重写原数组。 <?php require_once 'Zend/Session/Namespace.php'; $sessionNamespace = new Zend_Session_Namespace(); // assign the initial array $sessionNamespace->array = array('tree' => 'apple'); // make a copy of the array $tmp = $sessionNamespace->array; // modfiy the array copy $tmp['fruit'] = 'peach'; // assign a copy of the array back to the session namespace $sessionNamespace->array = $tmp; echo $sessionNamespace->array['fruit']; // prints "peach" Example #9 方案:存储包括引用的数组 作为选择,存储一个包含引用的数组到期望的数组,然后直接访问它。 <?php require_once 'Zend/Session/Namespace.php'; $myNamespace = new Zend_Session_Namespace('myNamespace'); $a = array(1, 2, 3); $myNamespace->someArray = array( &$a ); $a['foo'] = 'bar'; echo $myNamespace->someArray['foo']; // prints "bar" 在对象中使用会话
如果你计划在PHP会话中持久对象,要知道它们将为存储被 » 系列化。这样,任何在PHP会话中持久的对象在取出时一定会从存储中被去系列化。这意味着开发者必须确保持久对象的类必须在对象从会话存储中被去系列化之前被定义。如果一个非系列化的对象的类没有被定义,那么它就变成一个 在单元测试中使用会话
Zend Framework利用PHPUnit来促进自身代码的测试。大多数开发者在他们的应用程序中,扩展已有的一组单元测试,以覆盖测试他们的代码。在运行单元测试时,如果在结束会话之后使用了写相关的方法,那么会抛出"当前Zend_Session被标记为只读"的异常。在单元测试中使用Zend_Session需要额外的注意,因为在关闭会话(
围绕这一工作,参见 Example #10 PHPUnit Testing Code Dependent on Zend_Session <?php // testing setExpirationSeconds() require_once 'tests/Zend/Session/SessionTestHelper.php'; // also see SessionTest.php $script = 'SessionTestHelper.php'; $s = new Zend_Session_Namespace('space'); $s->a = 'apple'; $s->o = 'orange'; $s->setExpirationSeconds(5); Zend_Session::regenerateId(); $id = Zend_Session::getId(); session_write_close(); // release session so process below can use it sleep(4); // not long enough for things to expire exec($script . "expireAll $id expireAll", $result); $result = $this->sortResult($result); $expect = ';a === apple;o === orange;p === pear'; $this->assertTrue($result === $expect, "iteration over default Zend_Session namespace failed; expecting result === '$expect', but got '$result'"); sleep(2); // long enough for things to expire (total of 6 seconds waiting, but expires in 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(); // resume artificially suspended session // We could split this into a separate test, but actually, if anything leftover from above // contaminates the tests below, that is also a bug that we want to know about. $s = new Zend_Session_Namespace('expireGuava'); $s->setExpirationSeconds(5, 'g'); // now try to expire only 1 of the keys in the namespace $s->g = 'guava'; $s->p = 'peach'; $s->p = 'plum'; session_write_close(); // release session so process below can use it sleep(6); // not long enough for things to expire exec($script . "expireAll $id expireGuava", $result); $result = $this->sortResult($result); session_start(); // resume artificially suspended session $this->assertTrue($result === ';p === plum', "iteration over named Zend_Session namespace failed (result=$result)");
|