会话库

会话类允许您维护用户的“状态”并跟踪他们在浏览您的网站时的活动。

CodeIgniter 附带了一些会话存储驱动程序,您可以在目录表最后一部分看到。

使用会话类

初始化会话

会话通常会在每个页面加载时全局运行,因此 Session 类应该被自动初始化。

要访问和初始化会话

<?php

$session = \Config\Services::session($config);

$config 参数是可选的 - 您的应用程序配置。如果未提供,服务注册将实例化您的默认配置。

加载后,可以使用以下方法访问 Sessions 库对象

$session

或者,您可以使用辅助函数,该函数将使用默认配置选项。此版本更易于阅读,但不接受任何配置选项。

<?php

$session = session();

会话是如何工作的?

当加载页面时,会话类将检查用户浏览器是否发送了有效的会话 cookie。如果会话 cookie **不存在**(或与服务器上存储的 cookie 不匹配或已过期),则会创建一个新的会话并保存。

如果存在有效的会话,则会更新其信息。每次更新时,如果配置为这样做,会话 ID 可能会重新生成。

重要的是要了解,一旦初始化,Session 类就会自动运行。您无需执行任何操作即可使上述行为发生。您可以(正如您将在下面看到的那样)使用会话数据,但读取、写入和更新会话的过程是自动的。

注意

在 CLI 下,Session 库将自动停止,因为这是一个完全基于 HTTP 协议的概念。

关于并发性的说明

除非您正在开发一个使用大量 AJAX 的网站,否则您可以跳过此部分。但是,如果您正在开发这样的网站,并且遇到性能问题,那么此说明正是您要寻找的。

CodeIgniter v2.x 中的会话没有实现锁定,这意味着使用相同会话的两个 HTTP 请求可以完全同时运行。用更专业的术语来说 - 请求是非阻塞的。

但是,在会话的上下文中,非阻塞请求也意味着不安全,因为在一个请求中对会话数据的修改(或会话 ID 重新生成)可能会干扰第二个并发请求的执行。这个细节是许多问题的根源,也是 CodeIgniter 3 拥有完全重写的 Session 库的主要原因。

为什么要告诉您这些?因为在尝试找到性能问题的原因后,您可能会得出结论,锁定是问题,因此会研究如何删除锁定……

不要这样做!删除锁定是 **错误的**,它会给您带来更多问题!

锁定不是问题,而是解决方案。您的问题是您仍然打开了会话,而您已经处理了它,因此不再需要它。因此,您需要做的是在不再需要它时关闭当前请求的会话。

<?php

$session->close();

什么是会话数据?

会话数据只是一个与特定会话 ID(cookie)关联的数组。

如果您以前在 PHP 中使用过会话,那么您应该熟悉 PHP 的 $_SESSION 超级全局变量(如果没有,请阅读该链接上的内容)。

CodeIgniter 通过相同的方式访问其会话数据,因为它使用 PHP 提供的会话处理程序机制。使用会话数据就像操作(读取、设置和取消设置值)$_SESSION 数组一样简单。

注意

一般来说,使用全局变量是一种不好的做法。因此,不建议直接使用超级全局变量 $_SESSION

此外,CodeIgniter 还提供了两种特殊的会话数据类型,将在下面进一步解释:FlashdataTempdata

注意

出于历史原因,我们将不包括 Flashdata 和 Tempdata 的会话数据称为“userdata”。

检索会话数据

会话数组中的任何信息都可以通过 $_SESSION 超级全局变量获得。

<?php

$item = $_SESSION['item'];

或者通过传统的访问器方法。

<?php

$item = $session->get('item');

或者通过魔术获取器。

<?php

$item = $session->item;

甚至可以通过会话助手方法。

<?php

$item = session('item');

其中 item 是与您要获取的项目相对应的数组键。例如,要将之前存储的 name 项目分配给 $name 变量,您将执行以下操作。

<?php

$name = $_SESSION['name'];

// or:

$name = $session->name;

// or:

$name = $session->get('name');

注意

如果要访问的项目不存在,则 get() 方法将返回 null。

如果您想检索所有现有的会话数据,您可以简单地省略项目键(魔术获取器仅适用于单个属性值)。

<?php

$userData = $_SESSION;
// or:
$userData = $session->get();

重要

当通过键检索单个项目时,get() 方法将返回 flashdata 或 tempdata 项目。但是,当从会话中获取所有数据时,它不会返回 flashdata 或 tempdata。

添加会话数据

假设某个特定用户登录到您的网站。身份验证后,您可以将他们的用户名和电子邮件地址添加到会话中,使这些数据在全局范围内可用,而无需在需要时运行数据库查询。

您可以简单地将数据分配给 $_SESSION 数组,就像任何其他变量一样。或者作为 $session 的属性。

您可以将包含新会话数据的数组传递给 set() 方法

<?php

$session->set($array);

其中 $array 是一个包含新数据的关联数组。以下是一个示例

<?php

$newdata = [
    'username'  => 'johndoe',
    'email'     => '[email protected]',
    'logged_in' => true,
];

$session->set($newdata);

如果您想一次添加一个会话数据值,set() 也支持此语法

<?php

$session->set('some_name', 'some_value');

如果您想验证会话值是否存在,只需使用 isset() 检查

<?php

// returns false if the 'some_name' item doesn't exist or is null,
// true otherwise:
if (isset($_SESSION['some_name'])) {
    // ...
}

或者您可以调用 has()

<?php

$session->has('some_name');

将新值推送到会话数据

使用 push() 方法将新值推送到作为数组的会话值。例如,如果 hobbies 键包含一个爱好数组,您可以像这样将新值添加到数组中

<?php

$session->push('hobbies', ['sport' => 'tennis']);

删除会话数据

与任何其他变量一样,可以通过 unset() 取消设置 $_SESSION 中的值

<?php

unset($_SESSION['some_name']);
// or multiple values:
unset(
    $_SESSION['some_name'],
    $_SESSION['another_name']
);

同样,就像 set() 可用于向会话添加信息一样,remove() 可用于通过传递会话键来删除信息。例如,如果您想从会话数据数组中删除 some_name

<?php

$session->remove('some_name');

此方法还接受要取消设置的项目键数组

<?php

$array_items = ['username', 'email'];
$session->remove($array_items);

闪存数据

CodeIgniter 支持“闪存数据”,或仅在下一个请求中可用的会话数据,然后会自动清除。

这非常有用,尤其适用于一次性信息、错误或状态消息(例如:“已删除记录 2”)。

需要注意的是,闪存数据变量是常规的会话变量,在 CodeIgniter 会话处理程序中进行管理。

要将现有项目标记为“flashdata”

<?php

$session->markAsFlashdata('item');

如果要将多个项目标记为flashdata,只需将键作为数组传递

<?php

$session->markAsFlashdata(['item', 'item2']);

添加flashdata

<?php

$_SESSION['item'] = 'value';
$session->markAsFlashdata('item');

或者,使用 setFlashdata() 方法

<?php

$session->setFlashdata('item', 'value');

您也可以将数组传递给 setFlashdata(),与 set() 的方式相同。

读取flashdata变量与通过 $_SESSION 读取常规会话数据相同

<?php

$item = $_SESSION['item'];

重要

当通过键检索单个项目时,get() 方法将返回flashdata项目。但是,它不会在从会话中获取所有数据时返回flashdata。

但是,如果您想确保正在读取“flashdata”(而不是任何其他类型),您也可以使用 getFlashdata() 方法

<?php

$session->getFlashdata('item');

注意

如果找不到项目,getFlashdata() 方法将返回 null。

或者,要获取包含所有flashdata的数组,只需省略键参数

<?php

$session->getFlashdata();

如果您发现需要在额外的请求中保留flashdata变量,您可以使用 keepFlashdata() 方法。您可以传递单个项目或要保留的flashdata项目的数组。

<?php

$session->keepFlashdata('item');
$session->keepFlashdata(['item1', 'item2', 'item3']);

Tempdata

CodeIgniter 还支持“tempdata”,或具有特定过期时间的会话数据。在值过期或会话过期或被删除后,该值将自动删除。

与flashdata类似,tempdata变量由CodeIgniter会话处理程序在内部管理。

要将现有项目标记为“tempdata”,只需将它的键和过期时间(以秒为单位!)传递给 markAsTempdata() 方法

<?php

// 'item' will be erased after 300 seconds
$session->markAsTempdata('item', 300);

您可以通过两种方式将多个项目标记为tempdata,具体取决于您是否希望它们都具有相同的过期时间

<?php

// Both 'item' and 'item2' will expire after 300 seconds
$session->markAsTempdata(['item', 'item2'], 300);

// 'item' will be erased after 300 seconds, while 'item2'
// will do so after only 240 seconds
$session->markAsTempdata([
    'item'  => 300,
    'item2' => 240,
]);

添加tempdata

<?php

$_SESSION['item'] = 'value';
$session->markAsTempdata('item', 300); // Expire in 5 minutes

或者,使用 setTempdata() 方法

<?php

$session->setTempdata('item', 'value', 300);

您也可以将数组传递给 setTempdata()

<?php

$tempdata = ['newuser' => true, 'message' => 'Thanks for joining!'];
$session->setTempdata($tempdata, null, $expire);

注意

如果省略过期时间或将其设置为 0,将使用默认的生存时间值 300 秒(或 5 分钟)。

要读取tempdata变量,您也可以通过 $_SESSION 超全局数组访问它

<?php

$item = $_SESSION['item'];

重要

当通过键检索单个项目时,get() 方法将返回临时数据项。但是,它不会在从会话中获取所有数据时返回临时数据。

或者,如果您想确保您正在读取“临时数据”(而不是任何其他类型),您也可以使用 getTempdata() 方法

<?php

$session->getTempdata('item');

注意

如果找不到该项目,getTempdata() 方法将返回 null。

当然,如果您想检索所有现有的临时数据

<?php

$session->getTempdata();

如果您需要在临时数据值过期之前将其删除,您可以直接从 $_SESSION 数组中将其取消设置

<?php

unset($_SESSION['item']);

但是,这不会删除使该特定项目成为临时数据的标记(它将在下一个 HTTP 请求中失效),因此,如果您打算在同一个请求中重用相同的键,您需要使用 removeTempdata()

<?php

$session->removeTempdata('item');

关闭会话

close()

版本 4.4.0 中的新增功能。

要手动关闭当前会话,在您不再需要它之后,请使用 close() 方法

<?php

$session->close();

您不必手动关闭会话,PHP 会在您的脚本终止后自动关闭它。但是,由于会话数据被锁定以防止并发写入,因此一次只有一个请求可以对会话进行操作。您可以通过在对会话数据的所有更改完成后立即关闭会话来提高网站性能。

此方法的工作方式与 PHP 的 session_write_close() 函数完全相同。

销毁会话

destroy()

要清除当前会话(例如,在注销期间),您只需使用库的 destroy() 方法

<?php

$session->destroy();

此方法的工作方式与 PHP 的 session_destroy() 函数完全相同。

这必须是您在同一请求期间执行的最后一个与会话相关的操作。所有会话数据(包括闪存数据和临时数据)将被永久销毁。

注意

您不必从常规代码中调用此方法。清理会话数据而不是销毁会话。

stop()

自版本 4.3.5 起已弃用。

会话类还具有 stop() 方法。

警告

在 v4.3.5 之前,由于错误,此方法不会销毁会话。

从 v4.3.5 开始,此方法已修改为销毁会话。但是,它已弃用,因为它与 destroy() 方法完全相同。请改用 destroy() 方法。

访问会话元数据

在 CodeIgniter 2 中,会话数据数组默认包含 4 个项目:‘session_id’,‘ip_address’,‘user_agent’,‘last_activity’。

这是由于会话工作方式的具体细节,但现在我们的新实现不再需要。但是,您的应用程序可能依赖于这些值,因此以下是访问它们的替代方法

  • session_id: $session->session_idsession_id()(PHP 的内置函数)

  • ip_address: $_SERVER['REMOTE_ADDR']

  • user_agent: $_SERVER['HTTP_USER_AGENT'](会话未使用)

  • last_activity: 取决于存储,没有直接的方法。抱歉!

会话首选项

CodeIgniter 通常会让一切开箱即用。但是,会话是任何应用程序中非常敏感的组件,因此必须进行一些仔细的配置。请花时间考虑所有选项及其影响。

注意

自 v4.3.0 起,添加了新的 **app/Config/Session.php**。以前,会话首选项位于您的 **app/Config/App.php** 文件中。

您将在 **app/Config/Session.php** 文件中找到以下与 Session 相关的首选项。

首选项

默认值

选项

描述

driver

CodeIgniter\Session\Handlers\FileHandler

CodeIgniter\Session\Handlers\FileHandler CodeIgniter\Session\Handlers\DatabaseHandler CodeIgniter\Session\Handlers\MemcachedHandler CodeIgniter\Session\Handlers\RedisHandler CodeIgniter\Session\Handlers\ArrayHandler

要使用的 Session 存储驱动程序。

cookieName

ci_session

仅限 [A-Za-z_-] 字符

用于 Session Cookie 的名称。

expiration

7200 (2 小时)

以秒为单位的时间(整数)

您希望 Session 持续的秒数。如果您想要一个不失效的 Session(直到浏览器关闭),请将值设置为零:0

savePath

null

指定存储位置,取决于使用的驱动程序。

matchIP

false

true/false(布尔值)

是否在读取 Session Cookie 时验证用户的 IP 地址。请注意,一些 ISP 会动态更改 IP,因此如果您想要一个不失效的 Session,您可能需要将此设置为 false。

timeToUpdate

300

以秒为单位的时间(整数)

此选项控制 Session 类多久会自我更新并创建一个新的 Session ID。将其设置为 0 将禁用 Session ID 更新。

regenerateDestroy

false

true/false(布尔值)

在自动更新 Session ID 时,是否销毁与旧 Session ID 关联的 Session 数据。当设置为 false 时,数据将稍后由垃圾收集器删除。

注意

作为最后的手段,Session 库将尝试获取 PHP 的 Session 相关 INI 设置,以及 CodeIgniter 3 设置,例如 'sess_expire_on_close',当上述任何一个未配置时。但是,您不应该依赖这种行为,因为它会导致意外结果或在将来发生改变。请正确配置所有内容。

注意

如果 expiration 设置为 0,则 PHP 在 Session 管理中设置的 session.gc_maxlifetime 设置将按原样使用(通常是 1440 的默认值)。这需要在 php.ini 中或通过 ini_set() 进行更改。

除了上述值之外,Session Cookie 还使用您在 **app/Config/Cookie.php** 文件中的以下配置值。

首选项

默认值

描述

domain

‘’

Session 适用的域

path

/

会话适用的路径

secure

false

是否仅在加密(HTTPS)连接上创建会话 cookie

sameSite

Lax

会话 cookie 的 SameSite 设置

注意

httponly 设置对会话没有影响。出于安全原因,HttpOnly 参数始终启用。此外,Config\Cookie::$prefix 设置完全被忽略。

会话驱动程序

如前所述,Session 库附带 4 个处理程序或存储引擎,您可以使用它们

  • CodeIgniter\Session\Handlers\FileHandler

  • CodeIgniter\Session\Handlers\DatabaseHandler

  • CodeIgniter\Session\Handlers\MemcachedHandler

  • CodeIgniter\Session\Handlers\RedisHandler

  • CodeIgniter\Session\Handlers\ArrayHandler

默认情况下,当初始化会话时,将使用 FileHandler 驱动程序,因为它是最安全的选择,并且预计在任何地方都能正常工作(几乎所有环境都有文件系统)。

但是,如果您愿意,可以通过 app/Config/Session.php 文件中的 public $driver 行选择任何其他驱动程序。请记住,每个驱动程序都有不同的注意事项,因此在做出选择之前,请务必熟悉它们(如下)。

注意

ArrayHandler 用于测试期间,并将所有数据存储在 PHP 数组中,同时防止数据持久化。

FileHandler 驱动程序(默认)

‘FileHandler’ 驱动程序使用您的文件系统存储会话数据。

可以肯定地说,它的工作原理与 PHP 自己的默认会话实现完全相同,但如果这对您来说是一个重要的细节,请记住,它实际上不是相同的代码,并且有一些限制(和优势)。

更具体地说,它不支持 PHP 的 session.save_path 中使用的目录级别和模式格式,并且它将大多数选项硬编码以确保安全。相反,仅支持绝对路径 public string $savePath

您应该知道的另一件重要的事情是,确保不要使用可公开读取或共享的目录来存储您的会话文件。确保只有您有权查看所选 savePath 目录的内容。否则,任何可以这样做的人也可以窃取任何当前会话(也称为“会话固定”攻击)。

在类 Unix 操作系统上,这通常通过使用 chmod 命令将该目录的模式权限设置为 0700 来实现,这仅允许目录所有者对其执行读写操作。但请注意,运行脚本的系统用户通常不是您自己的用户,而是类似“www-data”之类的用户,因此仅设置这些权限可能会破坏您的应用程序。

相反,您应该根据您的环境执行以下操作

mkdir /<path to your application directory>/writable/sessions/
chmod 0700 /<path to your application directory>/writable/sessions/
chown www-data /<path to your application directory>/writable/sessions/

额外提示

你们中有些人可能会选择使用其他会话驱动程序,因为文件存储通常比较慢。这只是一半正确。

一个非常基本的测试可能会让您误以为 SQL 数据库更快,但在 99% 的情况下,只有在您只有少量当前会话时才会如此。随着会话数量和服务器负载的增加(这才是真正重要的时刻),文件系统将始终胜过几乎所有关系型数据库设置。

此外,如果性能是您唯一的关注点,您可能需要考虑使用 tmpfs(警告:外部资源),这可以使您的会话速度飞快。

DatabaseHandler 驱动程序

重要

由于其他平台上缺乏建议锁定机制,因此仅正式支持 MySQL 和 PostgreSQL 数据库。在没有锁的情况下使用会话会导致各种问题,尤其是在大量使用 AJAX 的情况下,我们不会支持此类情况。如果您遇到性能问题,请在处理完会话数据后使用 close() 方法。

“DatabaseHandler”驱动程序使用关系型数据库(如 MySQL 或 PostgreSQL)来存储会话。这是许多用户中流行的选择,因为它允许开发人员轻松访问应用程序中的会话数据——它只是数据库中的另一个表。

但是,必须满足一些条件

  • 您不能使用持久连接。

配置 DatabaseHandler

设置表名

为了使用 'DatabaseHandler' 会话驱动程序,您还需要创建我们之前提到的这个表,然后将其设置为您的 $savePath 值。例如,如果您想使用 'ci_sessions' 作为您的表名,您可以这样做

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Session\Handlers\FileHandler;

class Session extends BaseConfig
{
    // ...
    public string $driver = 'CodeIgniter\Session\Handlers\DatabaseHandler';

    // ...
    public string $savePath = 'ci_sessions';

    // ...
}
创建数据库表

然后当然,创建数据库表...

对于 MySQL

CREATE TABLE IF NOT EXISTS `ci_sessions` (
    `id` varchar(128) NOT null,
    `ip_address` varchar(45) NOT null,
    `timestamp` timestamp DEFAULT CURRENT_TIMESTAMP NOT null,
    `data` blob NOT null,
    KEY `ci_sessions_timestamp` (`timestamp`)
);

对于 PostgreSQL

CREATE TABLE "ci_sessions" (
    "id" varchar(128) NOT NULL,
    "ip_address" inet NOT NULL,
    "timestamp" timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL,
    "data" bytea DEFAULT '' NOT NULL
);

CREATE INDEX "ci_sessions_timestamp" ON "ci_sessions" ("timestamp");

注意

The id 值包含会话 cookie 名称 (Config\Session::$cookieName) 和会话 ID 以及分隔符。它应该根据需要增加,例如,当使用长会话 ID 时。

添加主键

您还需要添加一个主键,**取决于您的 $matchIP 设置**。以下示例在 MySQL 和 PostgreSQL 上都有效

// When $matchIP = true
ALTER TABLE ci_sessions ADD PRIMARY KEY (id, ip_address);

// When $matchIP = false
ALTER TABLE ci_sessions ADD PRIMARY KEY (id);

// To drop a previously created primary key (use when changing the setting)
ALTER TABLE ci_sessions DROP PRIMARY KEY;

重要

如果您没有添加正确的主键,可能会出现以下错误

Uncaught mysqli_sql_exception: Duplicate entry 'ci_session:***' for key 'ci_sessions.PRIMARY'
更改数据库组

默认情况下使用默认数据库组。您可以通过将 app/Config/Session.php 文件中的 $DBGroup 属性更改为要使用的组的名称来更改要使用的数据库组

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Session\Handlers\FileHandler;

class Session extends BaseConfig
{
    // ...
    public ?string $DBGroup = 'groupName';
}
使用命令设置数据库表

如果您不想手动完成所有这些操作,可以使用 cli 中的 make:migration --session 命令为您生成迁移文件

php spark make:migration --session
php spark migrate

此命令将在生成代码时考虑 $savePath$matchIP 设置。

RedisHandler 驱动程序

注意

由于 Redis 没有公开锁定机制,因此此驱动程序的锁由一个单独的值模拟,该值最多保留 300 秒。使用 v4.3.2 或更高版本,您可以使用 TLS 协议连接 Redis

Redis 是一个通常用于缓存的存储引擎,因为它具有高性能,这也是您可能使用 'RedisHandler' 会话驱动程序的原因。

缺点是它不像关系型数据库那样普遍,并且需要在系统上安装 phpredis PHP 扩展,而这个扩展并没有包含在 PHP 中。很有可能,只有在你已经熟悉 Redis 并将其用于其他目的时,你才会使用 RedisHandler 驱动。

配置 RedisHandler

与 'FileHandler' 和 'DatabaseHandler' 驱动程序一样,你还必须通过 $savePath 设置配置会话的存储位置。这里的格式有点不同,同时也很复杂。最好通过 phpredis 扩展的 README 文件来解释,所以我们只链接到它

然而,对于最常见的情况,一个简单的 host:port 对就足够了

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Session\Handlers\FileHandler;

class Session extends BaseConfig
{
    // ...
    public string $driver = 'CodeIgniter\Session\Handlers\RedisHandler';

    // ...
    public string $savePath = 'tcp://localhost:6379';

    // ...
}

MemcachedHandler 驱动程序

注意

由于 Memcached 没有公开锁定机制,因此此驱动程序的锁由一个单独的值模拟,该值最多保留 300 秒。

'MemcachedHandler' 驱动程序与其所有属性都非常类似于 'RedisHandler' 驱动程序,除了可用性,因为 PHP 的 Memcached 扩展是通过 PECL 分发的,一些 Linux 发行版将其作为易于安装的软件包提供。

除此之外,并且没有任何故意偏向 Redis,关于 Memcached 没有太多不同之处 - 它也是一个流行的产品,通常用于缓存,以其速度而闻名。

但是,值得注意的是,Memcached 给出的唯一保证是,将值 X 设置为在 Y 秒后过期将导致它在 Y 秒后被删除(但并不一定是在该时间之前不会过期)。这种情况很少发生,但应该考虑在内,因为它可能会导致会话丢失。

配置 MemcachedHandler

这里的 $savePath 格式相当简单,只是一个 host:port

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Session\Handlers\FileHandler;

class Session extends BaseConfig
{
    // ...
    public string $driver = 'CodeIgniter\Session\Handlers\MemcachedHandler';

    // ...
    public string $savePath = 'localhost:11211';

    // ...
}

额外提示

还支持使用可选的 weight 参数作为第三个冒号分隔的 (:weight) 值的多服务器配置,但我们必须注意,我们尚未测试其可靠性。

如果你想尝试使用此功能(自担风险),只需用逗号分隔多个服务器路径即可

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Session\Handlers\FileHandler;

class Session extends BaseConfig
{
    // ...

    // localhost will be given higher priority (5) here,
    // compared to 192.0.2.1 with a weight of 1.
    public string $savePath = 'localhost:11211:5,192.0.2.1:11211:1';

    // ...
}