kodus / session
一个简单接口,用于存储和检索会话数据,无需使用PHP的原生会话处理
Requires
- php: >=8.0
- kodus/uuid-v4: ^1.2
- kodus/uuid-v5: ^1.1
- middlewares/client-ip: ^2
- psr/http-message: ^1
- psr/http-server-handler: ^1
- psr/http-server-middleware: ^1
- psr/simple-cache: ^3
Requires (Dev)
- codeception/codeception: ^5
- codeception/module-asserts: ^3.0
- kodus/mock-cache: ^2
- nyholm/psr7: ^1.5
README
一个简单接口,用于存储和检索会话数据,无需使用PHP的原生会话处理。
安装
如果你的项目使用composer,只需要求此包
composer require kodus/session
简介
这个库提供了一种处理会话数据的方式,它促进了类型安全和简单性,而不依赖于PHP的原生会话处理。
通过将会话数据收集到简单的会话模型类中,这些类的属性和方法可以进行类型提示,并在会话中存储这些实例而不是单独存储值来实现类型安全。
这种方法需要额外的样板代码。这些会话模型的优势远远超过了编写小型简单数据模型的小量额外工作量。
会话的写操作会延迟到请求结束时,然后保存到存储中。这可以防止因关键错误而导致的会话状态损坏。
开始和结束会话是隐式发生的 - 例如,如果没有会话数据,会话将不会持续(并且不会发出任何会话cookie);同样,如果会话数据被清除并且/或者最后一个会话模型被垃圾回收,会话将自动终止。
会话模型
在会话中存储数据的第一步是定义你的会话模型。你可以将会话模型类视为你的会话数据的容器类。
一个很好的会话模型示例可能是用户会话模型。让我们看看用户会话,其中需要将用户的ID和全名存储在会话中
class UserSession implements SessionModel { private $user_id; private $full_name; public function setUserID(int $user_id) { $this->user_id = $user_id; } public function getUserID(): int { return $this->user_id ?: 0; } public function setFullName(string $full_name) { $this->full_name = $full_name; } public function getFullName(): string { return $this->full_name ?: ""; } public function isEmpty(): bool { return empty($this->user_id) && empty($this->full_name); } }
这些模型应该封装到当前域。换句话说,不要试图将所有会话数据收集到一个大的“通吃”会话模型中。
SessionModel
接口只要求你实现 isEmpty()
方法,因此你可以根据你的偏好定义会话模型,只要以下条件成立
-
SessionModel
的实例必须能够通过原生PHP序列化函数进行序列化和反序列化,而不会丢失数据。 -
SessionModel
的实例必须有一个不带参数的构造函数,或者所有构造函数参数的默认值。
强烈建议实现 SessionModel
的实现应仅具有与存储会话数据相关的属性和方法。
SessionModel::isEmpty(): bool
:当会话模型的状态与首次构造时相同,此方法才应返回true。在序列化会话模型时,用于垃圾收集,从存储中删除空模型。
会话
Session
接口定义了你的会话模型的定位器。通过调用 get
方法,你可以为当前会话获取会话模型。
get()
方法返回会话模型的引用。对实例所做的所有更改将在请求结束时存储。
/** @var UserSession $user_session */ $user_session = $session->get(UserSession::class);
会话模型始终可用
每个会话中只能有一个会话模型实例,并且它始终可用。如果会话模型的实例未存储在缓存中,则将创建一个新的实例,并通过get()
方法返回。
这意味着您可以假设会话模型的一个实例始终可用。
删除单个会话模型
由于会话模型始终可用,因此您永远不会直接删除会话模型。
相反,您的模型必须通过SessionModel::isEmpty()
方法指示是否将其视为空 - 如果是,则会在请求结束时由会话服务进行垃圾回收。
如果您的单个会话模型可以被“清除”,则您的模型必须定义这意味着什么 - 例如,以下模型实现支持一个clear()
方法
class UserSession implements SessionModel { private $user_id; public function clear() { unset($this->user_id); } public function isEmpty(): bool { return empty($this->user_id); } // ... }
清除会话状态
您可以在注销控制器/操作中清除整个会话。
$session->clear();
请注意,清除会话将孤儿通过get()
方法之前获取的任何对象。
清除会话还会隐式地续订会话,如以下所述。
续订会话
您可以在登录控制器/操作中续订现有会话。
$session->renew();
续订会话将保留当前会话状态,但更改会话ID,并销毁与旧会话ID关联的会话数据。
请注意,不建议定期续订会话ID - 发出新的会话ID应在认证后进行,例如在通过安全连接成功验证提供的登录凭据之后。
销毁特定会话
在罕见情况下,您可能希望销毁特定会话。例如,您可能希望强制销毁管理员阻止/禁止访问网站的用户的活跃会话。
您可以通过访问底层的SessionStorage
实现来完成此操作
$storage->delete($session_id);
请注意,这需要您跟踪用户的活跃会话ID,这超出了本包的范围 - 例如,您可以将最新的会话ID存储在数据库中用户表的一个列中。
如果您正在使用依赖注入容器,则应将SessionStorage
作为单独的组件引导,这样您就可以独立于SessionService
访问它。
Flash消息
Flash消息是一次性可读的消息,例如通知。使用会话模型概念,这变得非常简单
class Notifications implements SessionModel { private $notifications = []; public function add(string $message) { $this->notifications[] = $message; } public function take() { $notifications = $this->notifications; $this->notifications = []; return $notifications; } public function isEmpty(): bool { return count($this->notifications) == 0; } }
中间件
该包包括一个符合PSR-15的SessionMiddleware
,以便轻松集成SessionService
。
您可以使用此中间件与您的PSR-15兼容的中间件堆栈一起使用,或者将其用作创建更深入集成其余堆栈的自定义中间件的参考。
将此中间件放在中间件堆栈的顶部 - 它将初始化会话状态,然后无条件地委托给其余中间件堆栈,最后将任何更改提交到会话存储。
请求将装饰一个SessionData
实例,您可以使用它从请求中获取,在中间件堆栈的较低层,例如使用$request->getAttribute(SessionMiddleware::ATTRIBUTE_NAME)
。
会话服务
本节详细说明了如何将SessionService
集成到您的堆栈中。
要初始化会话(在请求开始时),使用beginSession()
方法,它返回一个SessionData
实例,代表您正在处理的请求的会话状态。
SessionData
实现了Session
,它定义了SessionData
的公共方面 - 例如,当通过构造函数注入提供时,对Session
进行类型提示。
要提交对会话状态的更改(在请求结束时),使用commitSession()
方法,它还会向PSR-7 ResponseInterface
实例添加一个会话cookie。
有关此模式的示例实现,请参阅SessionMiddleware
。
存储适配器
通过实现SessionStorage
接口可以自定义会话存储。
目前,kodus/session
包含一个 SimpleCacheAdapter
,它依赖于PSR-16缓存接口的实现,用于物理存储原始会话数据。
我们推荐使用ScrapBook包作为SimpleCacheAdapter
的缓存提供者。
初始化
以下是一个如何完全初始化会话抽象所有层的示例,从最低层的PDO
数据库连接,通过ScrapBook的SQLLite缓存提供者,到我们的PSR-16会话存储适配器,最后到PSR-15中间件。
use PDO; use MatthiasMullie\Scrapbook\Adapters\SQLite; use MatthiasMullie\Scrapbook\Psr16\SimpleCache; use Kodus\Session\Adapters\SimpleCacheAdapter; use Kodus\Session\SessionMiddleware; $connection = new PDO('sqlite:cache.db'); $cache = new SimpleCache(new SQLite($connection)); $storage = new SimpleCacheAdapter($cache); $service = new SessionService($storage); $middleware = new SessionMiddleware($service);