session

上文我们分析了 cookie 的实现,今天我们再来看看关于 session 的实现。

yii2 框架为我们提供了 session 组件 yii\web\Session 以便更好的管理 session 。

yii\web\Session 的定义跟 yii\web\CookieCollection 类似,但是有一个很明显的区别,父类不同

1
2
3
class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
{
}

yii\base\Component 和 yii\base\Object 的区别我们前面提过,如果你忘了,不要紧,仔细看下文,等会我们讲到 session 的配置兴许你就想起来了。

从 yii\web\Session 对各接口的实现可以看出,session 组件的实现主要是针对 $_SESSION 的操作,这一点跟原生 php 保持一致,我来简单解释一下。

首先我们来看一下 IteratorAggregate 接口。

我们知道,继承 IteratorAggregate 接口的类要实现 getIterator 方法,我们看一下 yii\web\Session::getIterator 方法的实现

1
2
3
4
5
public function getIterator()
{
$this->open();
return new SessionIterator;
}

yii\web\Session::open 方法等一下介绍,我们先看一下 yii\web\Session::getIterator 方法的返回值。

SessionIterator 很显然是自定义的 Iterator,这里指的是 yii\web\SessionIterator(代码就不贴了,大家找到这个类看一下该类对于 Iterator 的实现就好),从该迭代器的实现上可以看出,实现的是对 $_SESSION 的操作。

另外从 ArrayAccess 和 Countable 接口方法的实现上,都能看出跟 $_SESSION 有关。

我们知道,凡是 component,我们都可以在配置文件内对组件的属性进行配置。但是 session 组件,我们还可以配置更多非属性项,比如 id,name,savePath等等。

到现在为止,相信很多同学应该不陌生,这是因为以下两个原因

yii\web\Session 的父类是 yii\base\Component
yii\web\Session 有相应的 setKey 和 getKey 方法的实现
第一点我们一开始就知道了,第二点各位可以自己看一下 yii\web\Session 校验下。

至于其原因,前面介绍 yii\base\Component 应该跟大家提过了。

当然,yii\web\Session 还有其他对应的 getKey,setKey,你都可以在 session 组件上对 key 进行配置。

下面我们以一个完整操作,来看看 session 组件是怎么处理的。

session 的设置

1
2
$session = Yii::$app->session;
$session->set('language', 'en-US');

yii\web\Session::set 被调用之前,yii\web\Session::init 方法会被先调用

1
2
3
4
5
6
7
8
9
public function init()
{
parent::init();
register_shutdown_function([$this, 'close']);
if ($this->getIsActive()) {
Yii::warning('Session is already started', __METHOD__);
$this->updateFlashCounters();
}
}

我们把 init 分为四个部分

parent::init(),执行父类的 init 方法,在 yii2 内重载父类的 init 方法时,这个步骤往往少不得
register_shutdown_function 方法,注册 yii\web\Session::close 方法,该方法会在脚本执行结束的时候被自动调用,其实质是对 session_write_close 方法的调用
调用 yii\web\Session::getIsActive 方法,在 yii\web\Session 的实现上该方法会被多次调用,其含义是校验 session 是否被开启,很显然这里 session 还没有被开启,开启 session 需要调用 session_start 函数
如果 session 已经被开启了,则调用 updateFlashCounters 方法更新 flash 数据
前三点都很好理解,第四点提到的 flash 我们简单的看一下。

从字面上来理解flash,“闪”。意思是快,一晃而过。其实就是这个意思。

flash 机制,它是利用session组件提供的一种功能,算是一种福利吧,至少我觉得 session 组件是没有必要提供这样的功能的。

我们注意到 session 组件在 init 阶段,会判断 session 的状态,激活状态的情况下就会调用 yii\web\Session::updateFlashCounters 方法,这个方法做什么呢?

从 yii\web\Session::setFlash 方法中设置flash消息时我们可以发现,凡是 flash 的key,都会以下面这种形式保存一个计数

1
2
3
$counters = $this->get($this->flashParam, []);
$counters[$key] = $removeAfterAccess ? -1 : 0;
$_SESSION[$this->flashParam] = $counters;

而在 yii\web\Session::updateFlashCounters 方法调用时,会再更新这个 flash 计数,如果数值大于0,就认为对应的key失效了,便会unset掉。

有同学肯定想到了,setFlash的时候对flash key设置的是 -1 或者 0,那肯定是 getFlash 的时候设置了这个计数大于0,所以 yii\web\Session::updateFlashCounters 才会有意义。事实也正是如此。

flash就介绍这么多,我们回到前面的例子,接着 init ,看一下 yii\web\Session::set 方法的调用

1
2
3
4
5
public function set($key, $value)
{
$this->open();
$_SESSION[$key] = $value;
}

set 的过程分为两部分

调用 yii\web\Session::open 方法开启 session
$_SESSION 存储 session
yii\web\Session::open 方法会在程序内反复被调用,不要怀疑这里重复 open 会出什么问题,程序上已经避免了我们的担心。

相应的,还有获取 session 的方法,yii\web\Session::get

1
2
3
4
5
public function get($key, $defaultValue = null)
{
$this->open();
return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
}

大家有没有注意到,yii2 内部有很多类似 yii\web\Session::get 方法设置默认值的操作,这一点可以避免获取不到又额外再判断的麻烦,值得我们学习。

我们来对 yii\web\Session::open 方法拆解一下

1、调用 yii\web\Session::getIsActive 方法判断当前 session 的状态,如果已经激活,open 算是完事了,直接 return

1
2
3
if ($this->getIsActive()) {
return;
}

2、注册 session handler

1
$this->registerSessionHandler();

yii\web\Session::registerSessionHandler 方法,实质是对 session_set_save_handler 方法的封装,我们知道,这个方法可以让我们重写 session 机制。

从 yii\web\Session::registerSessionHandler 方法中可以看出,我们有两种重写 session 的方案。

配置 yii\web\Session::handler 属性是一个继承 \SessionHandlerInterface 的 object
重写 yii\web\Session,让 yii\web\Session::getUseCustomStorage 方法 return true,并覆盖 yii\web\Session 的以下方法

1
2
3
4
5
6
yii\web\Session::openSession
yii\web\Session::closeSession
yii\web\Session::readSession
yii\web\Session::writeSession
yii\web\Session::destroySession
yii\web\Session::gcSession

有同学好奇,为啥要重写 session?

默认的,session 是序列化之后保存在文件内的,总有一天你会想把 session 保存到 redis、mysql 等其他介质中,这个时候,我上面的啰嗦就体现出价值了。

php 手册上也有简单地案例,去参考参考。

3、设置 cookie 参数

1
$this->setCookieParamsInternal();

cookie 参数的修改,可以在 php.ini 配置文件中操作,我们也可以在程序运行时临时调用 session_set_cookie_params 函数修改。当然,你还可以通过指定 session 组件的 cookieParams 项来做配置。

4、开启 session

1
@session_start();

5、再次判断 session 是否开启,如果开启了,更新 flash 数据,否则记录异常信息

1
2
3
4
5
6
7
8
if ($this->getIsActive()) {
Yii::info('Session started', __METHOD__);
$this->updateFlashCounters();
} else {
$error = error_get_last();
$message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
Yii::error($message, __METHOD__);
}

看完 session 设置的过程,就不难理解 session 获取、删除等操作的实现。

无非就是先 open,然后再从 $_SESSION 中获取或者 unset 等操作。

如果要彻底删除 session,比如常见的退出操作,就需要调用 yii\web\Session::destroy 方法了。destroy 方法会销毁所有 session 相关的数据。

关于 session 组件,暂时就说这么多,下一节我们来分析一下 User 组件到底是怎么回事。