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


    说明:本文主要通过学习Laravel的session源码学习Laravel是如何设计session的,将自己的学习心得分享出来,希望对别人有所帮助。Laravel在web middleware中定义了session中间件IlluminateSessionMiddlewareStartSession::class,并通过该中间件来设计session,这个中间件的主要工作分为三步:(1)启动session,通过session handler从一些存储介质如redis中读取session值;(2)操作session,对session数据CRUD增删改查操作;(3)关闭session,把session_id写入到response header中,默认是laravel_session

    开发环境:Laravel5.3 + PHP7

    启动Session

    首先看下IlluminateSessionMiddlewareStartSession::class中间件源码中handle()方法:

        public function handle($request, Closure $next)
        {
            // 前置操作
            $this->sessionHandled = true;
    
            if ($this->sessionConfigured()) {
                // Start session.
                /**
                 * @var IlluminateSessionStore $session
                 */
                $session = $this->startSession($request);
    
                $request->setSession($session);
    
                $this->collectGarbage($session);
            }
    
            $response = $next($request);
    
            // 后置操作
            if ($this->sessionConfigured()) {
                $this->storeCurrentUrl($request, $session);
    
                $this->addCookieToResponse($response, $session);
            }
    
            return $response;
        }

    Laravel学习笔记之Middleware源码解析这篇文章中知道,该中间件有前置操作和后置操作。看下sessionConfigured()的源码:

        /**
         * Determine if a session driver has been configured.
         *
         * @return bool
         */
        protected function sessionConfigured()
        {
            // 检查session.php中driver选项是否设置
            return ! is_null(Arr::get($this->manager->getSessionConfig(), 'driver'));
        }
        
        // IlluminateSessionSessionManager
        /**
         * Get the session configuration.
         *
         * @return array
         */
        public function getSessionConfig()
        {
            return $this->app['config']['session'];
        }

    首先中间件检查session.php中driver选项是否设置,这里假设设置为经常使用的redis作为session的存储介质,并且需要在database.php中设置下redis的链接,本地需要装好redis,通过redis-cli命令查看redis是否已经安装好。OK,然后中间件使用startSession()方法来启动session:

        protected function startSession(Request $request)
        {
            /**
             * @var IlluminateSessionStore $session
             */
            $session = $this->getSession($request); // 获取session实例,Laravel使用Store类来管理session
    
            $session->setRequestOnHandler($request);
    
            // Load the session data from the store repository by the handler.
            $session->start();
    
            return $session;
        }
        
        public function getSession(Request $request)
        {
            /**
             * Get the session store instance via the driver.
             *
             * @var IlluminateSessionStore $session
             */
            $session = $this->manager->driver();
    
            /**
             * $session->getName() === 'laravel_session' === config('session.cookie')
             */
            $session->setId($request->cookies->get($session->getName()));
    
            return $session;
        }

    startSession()主要分为两步:获取session实例IlluminateSessionStore,主要步骤是$session = $this->manager->driver();通过该实例从存储介质中读取该次请求所需要的session数据,主要步骤是$session->start()。首先看下第一步的源码:

        // IlluminateSupportManager
        public function driver($driver = null)
        {
            // $driver = 'redis'
            $driver = $driver ?: $this->getDefaultDriver();
           
            if (! isset($this->drivers[$driver])) {
                $this->drivers[$driver] = $this->createDriver($driver);
            }
    
            return $this->drivers[$driver];
        }
        
        protected function createDriver($driver)
        {
            $method = 'create'.Str::studly($driver).'Driver';
    
            if (isset($this->customCreators[$driver])) {
                return $this->callCustomCreator($driver);
            } elseif (method_exists($this, $method)) { // 判断IlluminateSessionSessionManager中是否存在createRedisDriver()方法
                // 存在,call这个createRedisDriver()方法
                return $this->$method();
            }
    
            throw new InvalidArgumentException("Driver [$driver] not supported.");
        }
        
        // IlluminateSessionSessionManager
        public function getDefaultDriver()
        {
            // 返回 'redis'
            return $this->app['config']['session.driver'];
        }

    从以上源码中很容易知道,选择的driver是redis,最后还是要调用IlluminateSessionSessionManager中的createRedisDriver()方法:

        protected function createRedisDriver()
        {
            /**
             * @var IlluminateSessionCacheBasedSessionHandler $handler
             */
            $handler = $this->createCacheHandler('redis');
    
            // 设置redis连接
            $handler->getCache()->getStore()->setConnection($this->app['config']['session.connection']);
    
            return $this->buildSession($handler);
        }
        
        protected function createCacheHandler($driver)
        {
            // $store = 'redis'
            $store = $this->app['config']->get('session.store') ?: $driver;
    
            $minutes = $this->app['config']['session.lifetime'];
            
            // $this->app['cache']->store($store)返回IlluminateCacheRepository实例
            return new CacheBasedSessionHandler(clone $this->app['cache']->store($store), $minutes);
        }
        
        // IlluminateSessionCacheBasedSessionHandler
        /**
         * Get the underlying cache repository.
         *
         * @return IlluminateContractsCacheRepository|IlluminateCacheRepository
         */
        public function getCache()
        {
            return $this->cache;
        }
        
        // IlluminateCacheRepository
        /**
         * Get the cache store implementation.
         *
         * @return IlluminateContractsCacheStore|RedisStore
         */
        public function getStore()
        {
            return $this->store;
        }
        
        // IlluminateCacheRedisStore
        /**
         * Set the connection name to be used.
         *
         * @param  string  $connection
         * @return void
         */
        public function setConnection($connection)
        {
            $this->connection = $connection;
        }

    从以上源码知道获取到IlluminateSessionCacheBasedSessionHandler这个handler后,就可以buildSession()了:

        protected function buildSession($handler)
        {
            // 设置加密的则返回EncryptedStore实例,这里假设没有加密
            if ($this->app['config']['session.encrypt']) {
                return new EncryptedStore(
                    $this->app['config']['session.cookie'], $handler, $this->app['encrypter']
                );
            } else {
                // 默认$this->app['config']['session.cookie'] === 'laravel_session'
                return new Store($this->app['config']['session.cookie'], $handler);
            }
        }

    从源码中可看出session实例就是IlluminateSessionStore实例,并且构造Store类还需要一个重要的部件handler,构造好了session实例后,就可以通过这个handler来从session存储的介质中如redis获取session数据了,这里设置的session driver是redis,所以handler就会是IlluminateSessionCacheBasedSessionHandler。总的来说,现在已经构造好了session实例即IlluminateSessionStore

    然后第二步就是$session->start()从存储介质中加载session数据:

        public function start()
        {
            // 从存储介质中加载session数据
            $this->loadSession();
            
            // session存储介质中没有'_token'这个key就生成一个
            if (! $this->has('_token')) {
                $this->regenerateToken();
            }
    
            return $this->started = true;
        }

    关键是loadSession()的源码:

        // Illuminate/Session/Store
        protected function loadSession()
        {
            // 从redis中读取key为'laravel_session'的数据后存入session实例即Store的$attributes属性中
            $this->attributes = array_merge($this->attributes, $this->readFromHandler());
    
            foreach (array_merge($this->bags, [$this->metaBag]) as $bag) {
                /**
                 * @var SymfonyComponentHttpFoundationSessionStorageMetadataBag $bag
                 */
                $this->initializeLocalBag($bag);
    
                $bag->initialize($this->bagData[$bag->getStorageKey()]);
            }
        }
        
        protected function readFromHandler()
        {
            // 主要是这句,通过handler从存储介质redis中读取session数据
            // $this->getId() === 'laravel_session'
            $data = $this->handler->read($this->getId());
    
            if ($data) {
                $data = @unserialize($this->prepareForUnserialize($data));
    
                if ($data !== false && ! is_null($data) && is_array($data)) {
                    return $data;
                }
            }
    
            return [];
        }

    这里的handler是IlluminateSessionCacheBasedSessionHandler,看下该handler的read()源码:

        // $sessionId === 'laravel_session'
        public function read($sessionId)
        {
            // 这里的cache是IlluminateCacheRepository
            return $this->cache->get($sessionId, '');
        }
        
        // IlluminateCacheRepository
        public function get($key, $default = null)
        {
            if (is_array($key)) {
                return $this->many($key);
            }
    
            // 这里的store是IlluminateCacheRedisStore
            $value = $this->store->get($this->itemKey($key));
    
            if (is_null($value)) {
                $this->fireCacheEvent('missed', [$key]);
    
                $value = value($default);
            } else {
                $this->fireCacheEvent('hit', [$key, $value]);
            }
    
            return $value;
        }
        
        // IlluminateCacheRedisStore
        public function get($key)
        {
            if (! is_null($value = $this->connection()->get($this->prefix.$key))) {
                return $this->unserialize($value);
            }
        }

    通过以上代码,很容易了解从redis存储介质中加载key为'laravel_session'的数据,最后还是调用了RedisStore::get($key, $default)方法。

    但不管咋样,通过handle()第一步$session = $this->startSession($request);就得到了session实例即Store,该步骤中主要做了两步:一是Store实例化;二是从redis中读取key为'laravel_session'的数据。

    然后就是$this->collectGarbage($session)做了垃圾回收。中篇再聊。

    总结:本文主要学习了session机制的启动工作中第一步session的实例化,主要包括两步骤:Store的实例化;从redis中读取key为laravel_session的数据。中篇再聊下session垃圾回收,和session的增删改查操作,到时见。

  • 相关阅读:
    C# AtomicInt
    Ubuntu16.04或18.04上安装QQ微信迅雷
    Git强制拉取覆盖本地 Pull force
    ulimit限制打开的文件数量
    centos 7.x设置守护进程的文件数量限制
    Apache Doris通过supervisor进行进程管理
    CentOS7 安装supervisor守护进程管理器
    fdisk 分区
    linux i2c tools
    ubuntu12.04 登录黑屏
  • 原文地址:https://www.cnblogs.com/grimm/p/8461030.html
Copyright © 2020-2023  润新知