博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Laravel5.3之Session源码解析(上)
阅读量:6955 次
发布时间:2019-06-27

本文共 8774 字,大约阅读时间需要 29 分钟。

hot3.png

说明:本文主要通过学习Laravel的session源码学习Laravel是如何设计session的,将自己的学习心得分享出来,希望对别人有所帮助。Laravel在web middleware中定义了session中间件\Illuminate\Session\Middleware\StartSession::class,并通过该中间件来设计session,这个中间件的主要工作分为三步:

(1)启动session,通过session handler从一些存储介质如redis中读取session值;

(2)操作session,对session数据CRUD增删改查操作;

(3)关闭session,把session_id写入到response header中,默认是laravel_session

开发环境:Laravel5.3 + PHP7

启动Session

首先看下\Illuminate\Session\Middleware\StartSession::class中间件源码中handle()方法:

public function handle($request, Closure $next)    {        // 前置操作        $this->sessionHandled = true;        if ($this->sessionConfigured()) {            // Start session.            /** * @var \Illuminate\Session\Store $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;    }

从Laravel5.2之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'));    }        // \Illuminate\Session\SessionManager    /** * 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 \Illuminate\Session\Store $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 \Illuminate\Session\Store $session */        $session = $this->manager->driver();        /** * $session->getName() === 'laravel_session' === config('session.cookie') */        $session->setId($request->cookies->get($session->getName()));        return $session;    }

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

// \Illuminate\Support\Manager    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)) { // 判断\Illuminate\Session\SessionManager中是否存在createRedisDriver()方法            // 存在,call这个createRedisDriver()方法            return $this->$method();        }        throw new InvalidArgumentException("Driver [$driver] not supported.");    }        // \Illuminate\Session\SessionManager    public function getDefaultDriver()    {        // 返回 'redis'        return $this->app['config']['session.driver'];    }

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

protected function createRedisDriver()    {        /** * @var \Illuminate\Session\CacheBasedSessionHandler $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)返回\Illuminate\Cache\Repository实例        return new CacheBasedSessionHandler(clone $this->app['cache']->store($store), $minutes);    }        // Illuminate\Session\CacheBasedSessionHandler    /** * Get the underlying cache repository. * * @return \Illuminate\Contracts\Cache\Repository|\Illuminate\Cache\Repository */    public function getCache()    {        return $this->cache;    }        // \Illuminate\Cache\Repository    /** * Get the cache store implementation. * * @return \Illuminate\Contracts\Cache\Store|RedisStore */    public function getStore()    {        return $this->store;    }        // \Illuminate\Cache\RedisStore    /** * Set the connection name to be used. * * @param string $connection * @return void */    public function setConnection($connection)    {        $this->connection = $connection;    }

从以上源码知道获取到\Illuminate\Session\CacheBasedSessionHandler这个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实例就是\Illuminate\Session\Store实例,并且构造Store类还需要一个重要的部件handler,构造好了session实例后,就可以通过这个handler来从session存储的介质中如redis获取session数据了,这里设置的session driver是redis,所以handler就会是\Illuminate\Session\CacheBasedSessionHandler。总的来说,现在已经构造好了session实例即\Illuminate\Session\Store

然后第二步就是$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 \Symfony\Component\HttpFoundation\Session\Storage\MetadataBag $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是\Illuminate\Session\CacheBasedSessionHandler,看下该handler的read()源码:

// $sessionId === 'laravel_session'    public function read($sessionId)    {        // 这里的cache是Illuminate\Cache\Repository        return $this->cache->get($sessionId, '');    }        // Illuminate\Cache\Repository    public function get($key, $default = null)    {        if (is_array($key)) {            return $this->many($key);        }        // 这里的store是Illuminate\Cache\RedisStore        $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;    }        // Illuminate\Cache\RedisStore    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的增删改查操作,到时见。

转载于:https://my.oschina.net/botkenni/blog/790439

你可能感兴趣的文章
Linux下搭建ftp服务器(转载)
查看>>
hadoop之 HDFS-Hadoop存档
查看>>
搭建时间服务器
查看>>
php 多进程 父进程的阻塞与非阻塞
查看>>
asp.net core mvc ActionFilterAttribute 获取自动定义Attribute
查看>>
sealed、new、virtual、abstract与override 趣解
查看>>
[bzoj 4650][NOI 2016]优秀的拆分
查看>>
crossplatform---Nodejs in Visual Studio Code 08.IIS
查看>>
OGNL表达式入门
查看>>
Java 诊断工具 Arthas 教程学习笔记
查看>>
bootstrap 2.3版与3.0版的使用区别
查看>>
高并发之 - 全局有序唯一id Snowflake 应用实战
查看>>
菜鸟学习笔记1
查看>>
office2003 安装步骤及注意事项
查看>>
手机抓包图文教程 【转载】
查看>>
fprintf、fflush(stdout)、printf、sprintf与fprintf 的用法区分
查看>>
EPANET头文件解读系列3——TOOLKIT.H
查看>>
深入单例模式(理解理解)
查看>>
凸问题、分类器
查看>>
51nod 1574 排列转换
查看>>