• Laravel学习笔记之Session源码解析(中)


    说明:在上篇中学习了session的启动过程,主要分为两步,一是session的实例化,即IlluminateSessionStore的实例化;二是从session存储介质redis中读取id = laravel_session*的数据。Laravel5.3把session垃圾回收放在了启动过程中,尽管Laravel5.1是放在session关闭过程的,本篇聊下垃圾回收,这也是session第一步启动session的过程。session第二步就是操作session,包括对session数据的CRUD增删改查操作,本文也主要聊下相关操作源码。

    开发环境:Laravel5.3 + PHP7

    Session垃圾回收

    首先看下session中间件的源码IlluminateSessionMiddlewareStartSession::class

        public function handle($request, Closure $next)
        {
            $this->sessionHandled = true;
    
            if ($this->sessionConfigured()) {
                $session = $this->startSession($request);
    
                // 把session对象存储到Request中
                // 所以可以在控制器Controller中使用Request实例获取session对象:$request->session()
                $request->setSession($session);
    
                $this->collectGarbage($session);
            }   
            
            ...
            
         }  
         
         protected function collectGarbage(SessionInterface $session)
        {
            // 读取config/session.php中的配置
            $config = $this->manager->getSessionConfig();
    
            if ($this->configHitsLottery($config)) {
                // CacheBasedSessionHandler::gc(60) 60 minutes
                $session->getHandler()->gc($this->getSessionLifetimeInSeconds());
            }
        } 
        
        protected function configHitsLottery(array $config)
        {
            // session.php中'lottery'默认配置是[2, 100],这里就是取概率2/100 = 2%
            // 也就是100次请求有2次会触发过期session的垃圾回收
            return random_int(1, $config['lottery'][1]) <= $config['lottery'][0];
        } 

    这里假设session的存储介质是常用的redis,则$session->getHandler()返回的就是IlluminateSessionCacheBasedSessionHandler实例,该handler就是负责从redis这个存储介质中CRUD数据,OK,看下该handler的gc()源码:

        public function gc($lifetime)
        {
            return true;
        }

    其实什么都没做。这是当然的,redis对于过期的key会自动清除,所以这里就让redis来负责垃圾回收过期数据。当然,对于database这种handler,可以看下它的垃圾回收IlluminateSessionDatabaseSessionHandler:

        public function gc($lifetime)
        {
            $this->getQuery()->where('last_activity', '<=', time() - $lifetime)->delete();
        }

    以数据库作为存储session的介质,垃圾回收就是从sessions表里删除掉对应字段。

    操作Session

    操作Session就是对从存储介质如redis中取出的数据进行CRUD增删改查操作,包括:数据读取;数据存储;数据删除;数据暂存。当然,在对session进行CRUD操作前,首先得获取session对象即IlluminateSessionStore实例,有三种方法:通过Request实例;通过Session Facade方法;通过helper函数session(),代码如下:

        // 因为在中间件StartSesstion前置操作中有把session实例存入到$request中,$request->setSession($session);
        $session = $request->session(); 
        // 通过Session Facade直接获取到$session对象,并进行CRUD操作
        Session::put('session', 'Store'); 
        // 通过helper函数来获取session实例,实际上是通过app('session')从Container中解析出名为'session'的服务即Store实例
        $session = session()->driver(); 
    
        function session($key = null, $default = null)
        {
            if (is_null($key)) {
                return app('session');
            }
    
            if (is_array($key)) {
                return app('session')->put($key);
            }
    
            return app('session')->get($key, $default);
        }

    session数据读取

    session数据读取方法包括:

        // 'Store'是默认数据,读取key为'session:store'的数据
        $value = Session::get('session.store', 'Store'); 
        // IlluminateSessionStore
        public function get($name, $default = null)
        {
            return Arr::get($this->attributes, $name, $default);
        }
        
        // 'Store'是默认数据,读取key为'session:store'的数据,并删除key为'session'的数据
        $value = Session::pull('session', 'Store'); 
        // IlluminateSessionStore
        public function pull($key, $default = null)
        {
            return Arr::pull($this->attributes, $key, $default);
        }
        
        // 返回所有数据
        $value = Session:all();
        public function all()
        {
            return $this->attributes;
        }

    在Session启动过程中,就包含了把session数据从存储介质如redis中取出来,并存放在Store的$attributes属性中,可看Store::loadSession()源代码:

        protected function loadSession()
        {
            $this->attributes = array_merge($this->attributes, $this->readFromHandler());
    
            foreach (array_merge($this->bags, [$this->metaBag]) as $bag) {
                $this->initializeLocalBag($bag);
    
                $bag->initialize($this->bagData[$bag->getStorageKey()]);
            }
        }

    所以,使用Arr类的一些数组操作函数从Store的$attributes属性中读取session数据。Laravel提供了IlluminateSupportArr辅助类来操作数组,支持.语法来操作数组,同时还提供了IlluminateSupportStr辅助类来操作字符串。

    总之,Laravel提供了三种方法来读取session数据:

    Session::get();
    Session::pull();
    Session::all();

    session数据存储

    session数据存储方法包括:

        // '更新式存储',即如果redis中有'session.store'数据,就使用'Store'来update旧数据
        Session::put('session.store', 'Store');
        public function put($key, $value = null)
        {
            if (! is_array($key)) {
                $key = [$key => $value];
            }
    
            foreach ($key as $arrayKey => $arrayValue) {
                $this->set($arrayKey, $arrayValue);
            }
        }
        
        // '压入式存储',即如果redis中有'session.store'数据,就使用'Store'和旧数据如'StoreOld'作为新数组数据
        // 这时'session.store'新数据是['StoreOld', 'Store'];
        Session::push('session.store', 'Store');
        public function push($key, $value)
        {
            $array = $this->get($key, []);
    
            $array[] = $value;
    
            $this->put($key, $array);
        }

    总之,Laravel提供了两种方法来存储数据:

    Session::put('session.store', 'Store');
    Session::push('session.store', 'StoreNew');

    session数据删除

    session数据删除方法包括:

        // 删除key为'session.store'的数据
        Session::forget('session.store');
        public function forget($keys)
        {
            Arr::forget($this->attributes, $keys);
        }
        
        // 清空所有数据,$attributes为空
        Session::flush();
        public function flush()
        {
            $this->clear();
        }
        public function clear()
        {
            $this->attributes = [];
    
            foreach ($this->bags as $bag) {
                $bag->clear();
            }
        }

    总之,Laravel提供了两种方法来删除数据:

    Session::forget('session.store');
    Session::flush();

    session数据暂存

    数据暂存是把session中的数据保留到下一次请求中,下一次请求结束后则删除数据,数据暂存方法包括:

        // 把'session.store'数据刷到'_flash.new',等待下一次请求使用,然后再删除
        Session::flash('session.store', 'Store');
        public function flash($key, $value)
        {
            // 更新式存储'session.store' => 'Store'
            $this->put($key, $value);
    
            // 压入式存储'_flash.new' => ['session.store', XXX]
            $this->push('_flash.new', $key);
    
            // 删除'session.store'这个value值
            $this->removeFromOldFlashData([$key]);
        }
        protected function removeFromOldFlashData(array $keys)
        {
            // 把'_flash.old'数组中不包含'session.store'的结果存储到'_flash.old'中
            // 即删除'session.store'这个value值
            $this->put('_flash.old', array_diff($this->get('_flash.old', []), $keys));
        }
        
        // 把所有本次需要删除的数据全部刷到'_flash.new'中,等待下一次请求使用,然后再删除
        Session::reflash();
        public function reflash()
        {
            $this->mergeNewFlashes($this->get('_flash.old', []));
            
            $this->put('_flash.old', []);
        }
        protected function mergeNewFlashes(array $keys)
        {
            // 把'_flash.old'中的value值合并到'_flash.new'中
            $values = array_unique(array_merge($this->get('_flash.new', []), $keys));
    
            $this->put('_flash.new', $values);
        }
        
        // 把要删除的'session.store'重新激活,刷到'_flash.new'中,等待下一次使用
        Session::keep(['session.store' => 'Store']);
        public function keep($keys = null)
        {
            $keys = is_array($keys) ? $keys : func_get_args();
            
            // 把'session.store'刷到'_flash.new'中
            $this->mergeNewFlashes($keys);
            // 同时,把'session.store'从'_flash.old'中删除
            $this->removeFromOldFlashData($keys);
        }

    总之,就是把本次请求要删除的数据放在'_flash.old',留到下一次请求中使用的就把它刷到'_flash.new'中。Laravel提供了三种方法来暂存数据:

    Session::flash();
    Session::reflash();
    Session::keep();

    总结:本文主要学习下Laravel的session的垃圾回收和CRUD增删改查操作。下篇再学习下关闭session,到时见。

  • 相关阅读:
    一篇文章教会你利用Python网络爬虫实现豆瓣电影采集
    一篇文章教会你利用Python网络爬虫获取穷游攻略
    一篇文章教会你使用html+css3制作GIF图
    一篇文章教会你使用html+css3制作炫酷效果
    一篇文章教会你利用Python网络爬虫获取素材图片
    Spring 获取Bean ApplicationContextAware的使用
    数据库系统概论(第五版) 王珊 第三章课后习题答案
    【每日一题】27. 过河 (DP + 离散化)
    判断客户端是PC还是移动端问题的解决方案
    Mynavi Programming Contest 2021(AtCoder Beginner Contest 201)A ~ E题题解
  • 原文地址:https://www.cnblogs.com/grimm/p/8461042.html
Copyright © 2020-2023  润新知