Zend Framework1-Zend_Session

本文深入探讨了在PHP应用中利用Zend Framework进行会话管理的方法,包括基本用法、实例教程、开启会话、锁住会话命名空间、命名空间过期、会话封装和控制器及防止多重命名空间实例等方面。

1、简介

在基于 PHP 开发的 Web 应用程序中,会话(session) 代表服务器端和特定的用户代理客户端(比如 web 浏览器)之间的一对一的持久的状态数据。

Zend_Session 用来在由相同客户端发起的多个页面请求之间,管理和保护会话数据 。

逻辑上来说,会话数据是 cookie 数据的扩展。 和 cookie 数据不同,会话数据不储存在客户端,仅当服务器端程序源代码使得会话数据可用, 才通过回应来自客户端的请求与客户端共享会话数据。

在该组件及其文档中,术语“session data”代表服务器端存储在$_SESSION中的数据。该数据使用 Zend_Session 来管理,由Zend_Session_Namespace 采用对象的方式来控制。

会话命名空间(Session Namespaces) 提供了使用经典的命名空间方式来访问会话数据,命名空间逻辑上就是一系列被命名(键名为字符串)的联合数组(类似于普通的 PHP 数组)。

Zend_Session_Namespace 的实例用对象存储的方式实现了 $_SESSION 的命名空间。 Zend_Session 组件包装了PHP内置的会话模块,提供了一个管理会话的接口,也为Zend_Session_Namespace 持久化命名空间数据提供了API。Zend_Session_Namespace 为会话命名空间数据到PHP内部会话机制的持久化提供了一个标准的、面向对象的接口。Zend_Session 支持现有无名的会话,同时也支持“登录会话”。Zend Framework 的用户验证组件Zend_Auth,使用了 Zend_Session_Namespace,把用户的鉴别信息储存到了"Zend_Auth"命名空间下。 因为Zend_Session 使用了PHP内置的会话函数,常用的配置选项和设置都仍然适用(请参考» http://www.php.net/session)。 如此,储存在cookie或者URL中的会话标识,保持了客户端与服务器端会话状态数据之间的联系。

PHP 默认的 session_save_handler 函数 没有解决当客户端连接到了集群服务器中的一些服务器的情况下,保持客户端与服务器端会话状态数据之间联系的问题, 因为会话状态数据只能储存在本地服务器当中(译注:也就是在各服务器间不能共享session数据)。 当一组附加的且合理的save handlers可用时,我们会提供它。

2、基本用法

在Zend Framework中,Zend_Session_Namespace实例提供了操作会话数据主要的API。

命名空间常用于隔离所有的会话数据,尽管也为所有会话数据只需要一个命名空间的情况提供了一个默认的命名空间。

Zend_Session利用了PHP内置的会话模块(ext/session),以及它特有的$_SESSION全局数组做为会话状态数据的存储机制。

虽然$_SESSION在PHP的全局命名空间内仍然可以访问,但是开发者不应该直接访问它,这样Zend_SessionZend_Session_Namespace可以提供一组最可靠、安全的处理会话相关的功能。

每个Zend_Session_Namespace的实例对应于$_SESSION全局数组的一个条目,在那里命名空间被用作键。

<?php
require_once 'Zend/Session/Namespace.php';

$myNamespace = new Zend_Session_Namespace('myNamespace');

// $myNamespace corresponds to $_SESSION['myNamespace']
        
用Zend_Session直接和其它使用 $_SESSION的代码协同工作是可能的。然而,为避免问题,强烈建议这样的代码只使用$_SESSION中不和Zend_Session_Namespace的实例想对应的部分。

3、实例教程

在初始化Zend_Session时,如果没有指定命名空间,所有的数据将被透明地储存在'Default'命名空间下。

Zend_Session不打算直接处理会话命名空间容器的内容,取而代之,我们可以使用Zend_Session_Namespace

下面的例子演示了缺省命名空间的使用,和怎样计算用户访问页面的次数:

Example #1 页面浏览计数

<?php
require_once 'Zend/Session/Namespace.php';

$defaultNamespace = new Zend_Session_Namespace('Default');

if (isset($defaultNamespace->numberOfPageRequests)) {
    $defaultNamespace->numberOfPageRequests++; // 每次页面加载,这个将递增
} else {
    $defaultNamespace->numberOfPageRequests = 1; // 第一次
}

echo "Page requests this session: ", $defaultNamespace->numberOfPageRequests;
            

当多个模块使用Zend_Session_Namespace的实例拥有不同的命名空间,每个模块为它自己的会话数据获得数据封装。

可以传递给Zend_Session_Namespace构造函数一个可选的$namespace参数,它允许开发者分割会话数据到分离的命名空间。命名空间机制提供了一个有效的、流行的方法来确保处于命名空间之下的会话数据不遭到意外地改变。

命名空间的名称被限定为字符序列,它表示为不以下划线("_")开头的非空PHP字符串。只有Zend Framework的核心模块可使用以"Zend"开头的命名空间的名称。

Example #2 新方法: 使用命名空间避免冲突

<?php
require_once 'Zend/Session/Namespace.php';

// in the Zend_Auth component
$authNamespace = new Zend_Session_Namespace('Zend_Auth');
$authNamespace->user = "myusername";

// in a web services component
$webServiceNamespace = new Zend_Session_Namespace('Some_Web_Service');
$webServiceNamespace->user = "mywebusername";
            

上述例子中的代码与下面的代码有相同的效果,不过,上述例子中的会话对象把会话数据封装进了各自的命名空间。

Example #3 老方法: PHP会话访问

<?php
$_SESSION['Zend_Auth']['user'] = "myusername";
$_SESSION['Some_Web_Service']['user'] = "mywebusername";
            

4、迭代会话命名空间

Zend_Session_Namespace提供了 IteratorAggregate接口所有的能力,包括对foreach语句的支持:

Example #4 会话迭代

<?php
require_once 'Zend/Session/Namespace.php';

$aNamespace = new Zend_Session_Namespace('some_namespace_with_data_present');

foreach ($aNamespace as $index => $value) {
    echo "aNamespace->$index = '$value';\n";
}
            

5、会话命名空间的访问器

Zend_Session_Namespace实现__get(), __set(), __isset(), and __unset()这些魔术方法,除了在一个子类里,这些魔术方法不能被直接调用。相反,正常的操作符自动调用这些方法,如下例所示:

Example #5 访问会话数据

<?php
require_once 'Zend/Session/Namespace.php';

$namespace = new Zend_Session_Namespace(); // 缺省的命名空间

$namespace->foo = 100;

echo "\$namespace->foo = $namespace->foo\n";

if (!isset($namespace->bar)) {
    echo "\$namespace->bar not set\n";
}

unset($namespace->foo);
            

虽然在基本用法中讲到的利用Zend框架的会话管理的方法是完全可以接受的,但还有些最佳实践需要去考虑。这一节讨论会话处理和示例Zend_Session架构的更高级用法的精彩细节。

6、开启会话

如果你希望所有的请求都有个由Zend_Seesion管理的会话,那么请在程序的引导文件中开启它:

Example #1 开启全局会话

<?php
require_once 'Zend/Session.php';

Zend_Session::start();
            

在程序的引导文件中开启会话,可以避免引发会话开启之前已经有HTTP头发向用户浏览器的异常,那样可能会破坏web页面的美观。许多高级的特性需要先执行Zend_Session::start()(更多高级的特性之后会展开)。

使用Zend_Session组件,有4种开启会话的方法,其中2种是错误的。

  1. 错误的:不要开启PHP的session.auto_startsetting。如果你使用mode_php(或等同)并在php.ini中已经开启了该选项,而你又没有权限去关闭该选项,你可以在.htaccess文件(这个文件通常在HTML文档根目录下)中增加下面这一句:

    php_value session.auto_start 0
    
  2. 错误的:不要直接使用PHP的 session_start()函数。如果你直接使用session_start(),之后再使用Zend_Session_Namespace,那么Zend_Session::start()会抛出("会话已经开始")的异常。如果你在使用Zend_Session_Namespace或使用Zend_Session::start()后调用session_start(),那么会产生一个E_NOTICE级别的错误,且该调用将会被忽略。

  3. 正确的:使用Zend_Session::start()开启会话。如果你想让每个页面请求都开启会话,那么应该在ZF应用程序的引导文件(index.php)中尽早的调用这个函数。开启会话有些额外的开销,如果只有部分页面请求需要开启会话,那么就:

    • 在引导文件中,使用Zend_Session::setOptions()无条件地设置strict选项为true

    • 在任何Zend_Session_Namespace()对象初始化之前对需要使用会话的请求只调用Zend_Session::start()

    • 象往常一样,在需要会话的地方,使用"new Zend_Session_Namespace()",但必须确认先前已经调用过Zend_Session::start()了。

    strict 选项防止 new Zend_Session_Namespace() 自动调用 Zend_Session::start()。 这样,这个选项有利于应用程序的开发者强制执行一个设计原则以避免在某些页面请求中使用会话, 因为在调用 Zend_Session::start() 之前,实例化 Zend_Session_Namespace 时,会抛出一个异常。开发者需小心地考虑使用 Zend_Session::setOptions() 所引起的冲突,由于它们对应于基本的ext/session这些具有全局作用选项。

  4. 正确的:只要有需要使用会话的地方,就初始化new Zend_Session_Namespace(),并且基本的PHP会话将自动开启。这个极端简单的用法能在大多数的情形下很好地工作。然而,如果你使用地是默认的基于cookie的会话(强烈推荐使用这种方式),你必须确保在第一次调用new Zend_Session_Namespace()在任何PHP发向向客户端输出(例如,HTTP headers)之前

7、锁住会话命名空间

会话的命名空间可以加锁,以防止意外的变更该命名空间下的会话变量值。

使用lock()方法使某命名空间下会话变量为只读,unlock()方法使一个只读的名空间为可读写,isLocked()方法测试某命名空间是否已经被加锁。

加锁是短暂的,且只在此页面请求内有效,不会持续到下一个页面请求。给命名空间加锁不会影响到存储在该命名空间下对象的setter方法,但是阻止了命名空间的setter方法的移除或替换对象。

也就是说,虽给Zend_Session_Namespace的实例加了锁,但还是不能阻止它处同样引用了命名空间下数据的对它的变更。

Example #2 锁住会话命名空间

<?php
require_once 'Zend/Session/Namespace.php';

$userProfileNamespace = new Zend_Session_Namespace('userProfileNamespace');

// 标记会话设置为只读锁定
$userProfileNamespace->lock();

// 解锁只读锁定
if ($userProfileNamespace->isLocked()) {
$userProfileNamespace->unLock();
}       

8、命名空间过期

对于命名空间和在命名空间中的独立键,它们的寿命都是有限的。

在授权后,普通用例包括在请求之间传递临时信息,和通过除去访问潜在的敏感信息来降低一定的安全风险的暴露时有发生。

过期可以基于消逝的秒数或者跳步(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它们,然后再设置合适的键。

9、会话封装和控制器

命名空间可以被用来分离控制器对会话的访问,以免被污染。例如, 一个认证控制器可以为会议安全请求保持它的会话状态数据与其他控制器分离。

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
}
// ...
            

10、防止每个命名空间有多重实例

尽管session locking提供了很好的保护来防止意外的命名空间的会话数据的使用,Zend_Session_Namespace 也有能力防止给一个单个的命名空间创建多个实例。

为开启这个动作,当创建Zend_Session_Namespace的最后允许的实例,传递true给第二个构造函数参数。任何后来的初始化同一个命名空间的企图都会导致一个异常的抛出。

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";
}
            

上面构造函数的第二个参数告诉Zend_Session_Namespace任何之后带有"Zend_Auth"实例的命名空间都是不允许的。

企图创建这样的实例导致构造函数抛出一个异常。

如果在相同的请求期间稍后需要访问会话的命名空间,开发者因此有责任在其它地方给一个实例对象(在上面的例子中$authSpaceAccessor1$authSpaceAccessor2 或者$authSpaceAccessor3)存储一个引用。

例如,开发者可以存储引用到一个静态变量,添加一个引用给一个registry (参见Zend_Registry),或者使它对其它需要访问会话命名空间的方法可用。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值