mindplay/stately

该包已被弃用且不再维护。作者建议使用 kodus/session 包。

PSR-7 会话抽象

dev-master 2015-08-06 13:24 UTC

This package is auto-updated.

Last update: 2021-06-05 13:50:35 UTC


README

这个库实现了一个简单的会话抽象,它可以与 PSR-7 HTTP 抽象集成,以替代 PHP 的内置会话管理。

这意味着你将不会使用 $_SESSIONsession_*() 函数。

API 方法依赖于类型提示的闭包来创建/更新会话状态。实际上,这意味着每个会话变量都将是一个对象,这意味着你的会话状态将作为一个模型而不是数组或键值存储真正地暴露给你;这对于 IDE 支持和代码理解都是非常好的——例如

$product_id = 123;
$amount = 4;

$session->update(function (ShoppingCart $cart) use ($product_id, $amount) {
    $cart->items[] = new CartItem($product_id, $amount);
});

请注意,如果会话数据中尚不存在,将自动创建 ShoppingCart 模型。因此,从会话状态的角度来看,每个会话模型都是一个 "singleton",尽管你可以在测试套件中创建多个实例。

这类似于例如 $_SESSION['ShoppingCart'] = new ShoppingCart(),但消除了使用数组和字符串的不安全行为,不需要测试会话变量是否已设置,不需要在从 $_SESSION 读取时类型提示,等等。

如果你不希望自动创建缺失的模型,可以将参数类型提示为例如 ShoppingCart $cart = null - 当然,这隐含着你要在回调中使用 if-语句处理缺失的 $cart

此外,还可能使用多个参数在一个调用中获取多个会话模型。

您还可以从当前会话中 remove() 会话模型,或 clear() 容器的全部内容——有关文档,请参阅 SessionContainer 接口。

实现

你应该在中心位置创建 SessionService(通常在依赖注入容器中),并将其作为 SessionContainer 暴露给消费者(通常为控制器),这是接口的公开部分。接口的内部部分包括 commit() 方法,应该作为请求/响应管道(中间件、框架或前端控制器)的一部分集中调用,例如,在控制器被调派后,在你发出响应之前。

示例

请注意,自动创建意味着你的会话模型必须始终有一个空的构造函数——这意味着它不能有硬依赖。尽量将其视为“好事”——这意味着你不会想添加诸如为 $user_id 属性添加 getUser() 方法之类的操作,这将意味着依赖于某种类型的 UserRepository

这被称为“特性渴望”,是一种反模式——你试图提供一种更方便的方式来与模型交互,这会很好,但是你的模型现在实际上是一个服务而不是只是一个模型,即使它在内部使用存储库来提供该服务。

解决这个问题的一个干净的方法是创建一个单独的用户服务,该服务内部使用会话容器与会话模型交互——目的不是让消费者直接访问 $_SESSION 来获取组件的会话数据,就像预期消费者不会直接访问你的会话模型一样;而是将这种交换封装在服务内部进行。

例如

class ActiveUser
{
    /** @var int|null active user ID (or NULL, if no User is logged in) */
    public $user_id;
}

class UserService
{
    private $container;
    private $repo;

    public function __construct(SessionContainer $container, UserRepository $repo)
    {
        $this->container = $container;
        $this->repo = $repo;
    }

    /**
     * @return User
     */
    public function getUser()
    {
        $user_id = $this->container->update(function (ActiveUser $user) {
            return $user->user_id;
        });

        return $user_id
            ? $this->repo->getUserById($user_id)
            : null;
    }
}

现在,User 模型可以通过 UserService 方便地暴露出来,该服务内部使用 ActiveUser 模型来持久化会话。