资讯专栏INFORMATION COLUMN

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

NervosNetwork / 329人阅读

摘要:然后中间件使用方法来启动获取实例,使用类来管理主要分为两步获取实例,主要步骤是通过该实例从存储介质中读取该次请求所需要的数据,主要步骤是。

说明:本文主要通过学习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的增删改查操作,到时见。

欢迎关注Laravel-China。

RightCapital招聘Laravel DevOps

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30519.html

相关文章

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

    摘要:说明在上篇中学习了的启动过程,主要分为两步,一是的实例化,即的实例化二是从存储介质中读取的数据。第二步就是操作,包括对数据的增删改查操作,本文也主要聊下相关操作源码。下篇再学习下关闭,到时见。 说明:在上篇中学习了session的启动过程,主要分为两步,一是session的实例化,即IlluminateSessionStore的实例化;二是从session存储介质redis中读取id ...

    longshengwang 评论0 收藏0
  • Laravel学习笔记Session源码解析(下)

    摘要:实际上,在中关闭主要包括两个过程保存当前到介质中在中存入。,学习下关闭的源码吧先。总之,关闭的第二件事就是给添加。通过对的源码分析可看出共分为三大步启动操作关闭。总结本小系列主要学习了的源码,学习了的三大步。 说明:在中篇中学习了session的CRUD增删改查操作,本篇主要学习关闭session的相关源码。实际上,在Laravel5.3中关闭session主要包括两个过程:保存当前U...

    Awbeci 评论0 收藏0
  • Laravel学习笔记bootstrap源码解析

    摘要:总结本文主要学习了启动时做的七步准备工作环境检测配置加载日志配置异常处理注册注册启动。 说明:Laravel在把Request通过管道Pipeline送入中间件Middleware和路由Router之前,还做了程序的启动Bootstrap工作,本文主要学习相关源码,看看Laravel启动程序做了哪些具体工作,并将个人的研究心得分享出来,希望对别人有所帮助。Laravel在入口index...

    xiaoxiaozi 评论0 收藏0
  • Laravel学习笔记IoC Container实例化源码解析

    摘要:说明本文主要学习容器的实例化过程,主要包括等四个过程。看下的源码如果是数组,抽取别名并且注册到中,上文已经讨论实际上就是的。 说明:本文主要学习Laravel容器的实例化过程,主要包括Register Base Bindings, Register Base Service Providers , Register Core Container Aliases and Set the ...

    ningwang 评论0 收藏0
  • Laravel学习笔记Container源码解析

    摘要:实际上的绑定主要有三种方式且只是一种的,这些已经在学习笔记之实例化源码解析聊过,其实现方法并不复杂。从以上源码发现的反射是个很好用的技术,这里给出个,看下能干些啥打印结果太长了,就不粘贴了。 说明:本文主要学习Laravel中Container的源码,主要学习Container的绑定和解析过程,和解析过程中的依赖解决。分享自己的研究心得,希望对别人有所帮助。实际上Container的绑...

    huayeluoliuhen 评论0 收藏0

发表评论

0条评论

NervosNetwork

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<