在webman中写的单例有点问题

aphper

打算写一个webman的auth组件,参考Yii2的user组件

$auth = new \Webman\Auth\Auth();
$auth->isGuset();
$auth->login();
$auth->logout();

功能没有问题 ,但是每次都要new一下,在框架多个地方调用就要new多次实在不方便,于是做了一个单例

<?php
namespace support;

class Auth
{
    private static $instance;

    public static function getInstance()
    {
        if( self::$instance == false ){
            self::$instance = new \Webman\Auth\Auth();
        }

        return self::$instance;
    }

    public static function __callStatic($method, $args)
    {
        $instance = static::getInstance();

        if (! $instance) {
            throw new RuntimeException('未获取到 Webman\Auth\Auth 的实例');
        }

        return $instance->$method(...$args);
    }
}

一开始测试好像没啥问题

Auth::login()
Auth::isGuest()
Auth::user()->email
...

结果头痛的地方来了,使用support\Auth::method()这种方法调用时,每次我登录又退出后,就发现登录状态还在 session是删除了的,贴一下代码

<?php
namespace Webman\Auth;

//implements
class Auth
{

    //登录地址
    public $login_url  = '';

    public $return_url = '';

    public $session_id = '';

    public $identityClass = '';

    public $_identity;

    public function __construct($guard='web')
    {
        $config = config("auth.guards.{$guard}");

        if( $config && is_array($config) ){
            foreach ($config as $key => $value) {
                $this->$key = $value;
            }
        }
    }

    /**
     * 指定看守器
     */
    public function guard(string $guard)
    {
        return (new Auth($guard));
    }

    /**
     * 登录
     */
    public function login($user)
    {
        $user_id = $user->id();
        session([
            $this->session_id => $user_id
        ]);
    }

    /**
     * 退出
     */
    public function logout()
    {
        return request()->session()->forget( $this->session_id );
    }

    /**
     * 是否为游客
     * @return 布尔值
     */
    public function IsGuest()
    {
        return $this->user() === null;
    }

    /**
     * 强制登录 否则跳转至登录页面
     * webman中无法使用redirect
     */
    public function loginRequired()
    {
        if( $this->IsGuest() ){
            return redirect('/user');
        }
    }

    public function redirectLogin()
    {
        if( $this->IsGuest() ){
            return redirect($this->login_url);
        }
    }

    /**
     * 返回用户ID
     */
    public function id()
    {
        if( $this->_identity == false ){
            $this->renewAuthStatus();
        }

        return $this->_identity->id();
    }

    /**
     * 获取用户类实例
     */
    public function user()
    {
        if( $this->_identity == false ){
            $this->renewAuthStatus();
        }

        if(  $this->_identity){
            //@114行 登录过一次后这里还能取到值
            echo $this->_identity->nickname;
        }

        return $this->_identity;
    }

    public function renewAuthStatus()
    {
        $id = session($this->session_id);

        //读取用户信息
        if( $id == null ){
            $identity = null;
        }else{
            $class = $this->identityClass;
            $identity = $class::findIdentity($id);
        }

        //验证validateAuthKey
        if ($identity !== null){

        }

        $this->setIdentity($identity);
    }

    public function setIdentity($identity)
    {
        if( $identity instanceof \Webman\Auth\IdentityInterface ){
            $this->_identity = $identity;
        }elseif ($identity === null){
            //@149行 登录再退出 session删除了 这里把_identity设置为null 但是115行还能读取到之前的用户信息
            echo "_identity set null";
            $this->_identity = null;
        }else{
            throw new \Exception("identity must implements from IdentityInterface");
        }
    }

    public function returnUrl()
    {
        return $this->return_url;
    }
}

问题出在149和114行,149行明明把_identity设为null,代码也执行到了这里 114行的_identity却还是能取出用户数据

2045 5 1
5个回答

aphper

@walkor I need you

  • 暂无评论
aphper


退出时重置 $this->_identity 也没用 下一个请求他还是有值

  • 暂无评论
aphper

自问自答一下,问题已经解决,因为webman中单例和容器都是全局的,所以每次只在一次请求生命周期内有效的单例,直接放在request对象中即可,每次请求完毕webman都会帮你清理掉 也不会被其他用户取到这个变量

<?php
namespace support;

use support\Container;

class Auth
{

    public static function __callStatic($method, $args)
    {

        $request = request();
        if( !$request->auth_instance ){
            $request->auth_instance = new \Webman\Auth\Auth();
        }
        $instance = $request->auth_instance ;

        if (! $instance) {
            throw new RuntimeException('未获取到 Webman\Auth\Auth 的实例');
        }

        return $instance->$method(...$args);
    }

}
  • 暂无评论
walkor

用户状态数据不适合放长生命周期对象里(不适合单例),比如有1万个在线用户,理论上内存中就要存储1万个用户的_identity状态,如果你只存一个,那就是所有用户共享的,只要有一个用户登录了,其它所有用户就共享这个状态了,导致数据错乱。
另外webman是多进程的,进程A设置了某个用户的_identity状态,不会同步到进程B,如果这时候用户访问到了进程B,也会有问题。

长生命周期的对象适合用在所有用户可以共享的类上,例如数据库对象、redis对象、配置对象、或者一些不带状态数据的处理类如Log类等。

webman中的session对象也是每个请求重新new一次,保证用户状态数据是从磁盘或者存储中拿到的最新数据。

  • 暂无评论
CZZU

不需要单例,多次new不影响什么,你这属于多虑

  • 暂无评论
年代过于久远,无法发表回答
🔝