Laravel5.5源码详解 -- Session的启动分析

来源:互联网 发布:雅米网络兼职靠谱吗 编辑:程序博客网 时间:2024/05/31 19:26

Laravel5.5源码详解 – Session的启动分析

Session的整个过程包括三个主要流程(laravel默认的sesssion名称都是laravel_session),
(1)启动session,
(2)操作session,对数据进行CRUD增删改查操作,
(3)关闭session。

Session启动之后的操作,和数据库的操作类似。这里不打算讲解。这里只关注启动过程,其一是因为session本身,这里相对比较难理解,其二是因为,CSRF等都是在session的启动过程中传入进去的,所以有必要了解。

首先看一下调用关系,session作为中间件由pipeline引入,我们从session的handle函数开始,这个函数在
\Illuminate\Session\Middleware\StartSession::class。
public function handle(request,Closurenext)
{
$this->sessionHandled = true;

    // If a session driver has been configured, we will need to start the session here    // so that the data is ready for an application. Note that the Laravel sessions    // do not make use of PHP "native" sessions in any way since they are crappy.    if ($this->sessionConfigured()) {        $request->setLaravelSession(            $session = $this->startSession($request)        );        $this->collectGarbage($session);    }    $response = $next($request);    // Again, if the session has been configured we will need to close out the session    // so that the attributes may be persisted to some storage medium. We will also    // add the session identifier cookie to the application response headers now.    if ($this->sessionConfigured()) {        $this->storeCurrentUrl($request, $session);        $this->addCookieToResponse($response, $session);    }    return $response;}

其中Handle调用 startSession 再调用 getSession & setRequestOnHandler 。我们一步一步来,慢慢剥开这些外衣,

protected function startSession(Request $request){    return tap($this->getSession($request), function ($session) use ($request) {    //下面这句没细查,先跳过。我调试了下,没发现有干任何有意义的事情,可能是采用redis等时才需要,    //这里配置的是file。与其相关的接口是SessionHandlerInterface,这个一般是php对session接口设计的,    //如memcached, redis等。        $session->setRequestOnHandler($request);        //关键是后面这句        $session->start();    });}

startSession()包括两步,首先是获取session的实例,也就是\Illuminate\Session\Store,主要步骤是session=this->manager->driver(),下面随后讲解,第二步,就是通过该实例从存储介质中读取所需要的数据,相关代码在$session->start()中体现。

第一步的源码:获取session实例\Illuminate\Session\Store

在SessionStart.php里,

 public function getSession(Request $request){    return tap($this->manager->driver(), function ($session) use ($request) {        $session->setId($request->cookies->get($session->getName()));    });}

这个$this->manager->driver()是这么个东东?

Store {#325 ▼id: "4qbYEQItvRNIJKSfAeRO0rGT1S4JGk3bAuedRFEJ"name: "laravel_session"attributes: []handler: FileSessionHandler {#326 ▼    #files: Filesystem {#101}    #path: "D:\wamp64\www\laravel\larablog\storage\framework/sessions"    #minutes: "120"  }  started: false}

说明白点,$this->manager->driver()就相当于StartSession->SessionManager->Store。也就是session实例本尊。这个manager,就是SessionManger类。

那么laravel的sessionManager是什么时候配置store的呢?
本质上,是调用了父类Illuminate\Support\Manager里面的driver()函数,该函数又调用Illuminate\Session\SessionManager中的getDefaultDriver()函数。我们先看getDefaultDriver()函数
public function getDefaultDriver()
{
return $this->app[‘config’][‘session.driver’];
}
这个app[‘config’]的来源,后面还会详细讲解,这理先了解结果。总之,就是把/bootstrap/cache/config.php 里面的配置项session.driver拉出来。比如我的配置项是这样的,

'session' =>  array ('driver' => 'file','lifetime' => '120','expire_on_close' => false,'encrypt' => false,'files' => 'D:\\wamp64\\www\\laravel\\larablog\\storage\\framework/sessions',   …此处省略若干行 )

再看driver()函数。整体上,driver()函数是下面这个样子,其中的createDriver后面还会讲到,这个又和buildStore有关。

public function driver($driver = null){    //得到/bootstrap/cache/config.php 里面的配置项    $driver = $driver ?: $this->getDefaultDriver();    //如果这个store不存在的话,就创建一个,store实例就是在这创建的。    if (! isset($this->drivers[$driver])) {        $this->drivers[$driver] = $this->createDriver($driver);    }    return $this->drivers[$driver];}

最后this>manager>driver()this->drivers[“file”],也就是下面的store,

Store {#326 ▼id: "cp0yC4HOkgwAq2h9FfGeLF98RM3IgscXkENFRFnp"name: "laravel_session"attributes: []handler: FileSessionHandler {#327 ▼    #files: Filesystem {#101}    #path: "D:\wamp64\www\laravel\larablog\storage\framework/sessions"    #minutes: "120"  }started: false}

说明一下,这个id没有什么实质意义,每次刷新都会不同。

那么我们明白,startSession 首先运行的就是getSession(),其中得到的是store,也就是$session。
再注意getSession()中这一小行,$session->setId($request->cookies->get($session->getName()));
$request->cookies是在request创建时就传递进去的,是一个数组,如下,

cookies: ParameterBag {#21 ▼    #parameters: array:2 [▼    "XSRF-TOKEN" => "eyJpdiI6IllOSStvZlMwNEk0T084eWJVWFZ5VVE9PSI▶"    "laravel_session" => "eyJpdiI6IlRNSCtGcmlkeEEybGc5TzUzdXRyR ▶"    ] }

这里要说一句,就是读者不必去关注XSRF-TOKEN的具体值,因为调试时,每次都会不一样,在实际运行时,只有一个。

接上面,$session->getName()就是那个名称”laravel_session”,经过$session->setId()处理之后,会得到这么一个store,

Store {#325 ▼id: "0z8Sd3lsnJYIkTBILQeamqh80floYcF0InWwdgm2"name: "laravel_session"attributes: []handler: FileSessionHandler {#326 ▼    #files: Filesystem {#101}    #path: "D:\wamp64\www\laravel\larablog\storage\framework/sessions"    #minutes: "120"  }started: false}

还是前面提到的那个store,也是getSession的返回值。

第二步,读取session数据

startSession在得到$session(也就是store)之后,接下来会启动下面这个start,现在来看Session->start()

public function start(){    //最重要的一条语句,实质是读取保存的session数据    $this->loadSession();    //如果没有CSRF_TOKEN,就创建一个    if (! $this->has('_token')) {            $this->regenerateToken();    }    //session启动完毕(就是取到了需要的数据而已),返回    return $this->started = true;}protected function loadSession(){    $this->attributes = array_merge($this->attributes, $this->readFromHandler());}protected function readFromHandler(){       //read读到session中的数据    if ($data = $this->handler->read($this->getId())) {        //反序列化 -- 从已存储的session数据中创建 PHP 的值。        $data = @unserialize($this->prepareForUnserialize($data));        //确定读到的数据是非空数组。         if ($data !== false && ! is_null($data) && is_array($data)) {            return $data;        }    }    return [];}

在上面的函数中,readFromHandler里用到的这个handler是指向下面这个我们用来存储session数据的具体位置,

FileSessionHandler {#326 ▼files: Filesystem {#101}path: "D:\wamp64\www\laravel\larablog\storage\framework/sessions"minutes: "120"}

必须特别说明的是,这里我用的是laravel默认的file,也就是文件来存储session。所以,handler也就是FileSessionHandler。如果你的配置不一样,那么handler就会不一样。
继续深入readFromhandler之前,还要了解以下两点:store的创建和参数传递,及FileSessionHandler。

Store是什么时候被创建的?如何传递参数。

现在回过头来看前面提到的createDriver了,前面已经提到过,该函数被Illuminate\Support\Manager里面的driver()函数调用。其原型是这样子的,

    protected function createDriver($driver)    {        // We'll check to see if a creator method exists for the given driver. If not we        // will check for a custom driver creator, which allows developers to create        // drivers using their own customized driver creator Closure to create it.        if (isset($this->customCreators[$driver])) {            return $this->callCustomCreator($driver);        } else {            $method = 'create'.Str::studly($driver).'Driver';            if (method_exists($this, $method)) {                return $this->$method();            }        }        throw new InvalidArgumentException("Driver [$driver] not supported.");    }

这里面的$method,如果你打印出来的话,正是”createFileDriver”。所以,最终创建store的命令,也就是下面要提到的createFileDriver。这也是因为我们使用的是file作为store配置的结果,所以看这个

protected function createFileDriver(){    return $this->createNativeDriver();}protected function createNativeDriver(){    $lifetime = $this->app['config']['session.lifetime'];    return $this->buildSession(new FileSessionHandler(        $this->app['files'], $this->app['config']['session.files'], $lifetime   ));}protected function buildSession($handler){    if ($this->app['config']['session.encrypt']) {        return $this->buildEncryptedSession($handler);    } else {        //默认的情况下是不加密的session,所以执行下面这句,        return new Store($this->app['config']['session.cookie'], $handler);    }}

最后这里createFileDriver调用了createNativeDriver,其中又用new createNativeDriver创建了这个handler作为参数,传递给buildSession ,buildSession再用这个handler创建了store。最重要的,是要了解new Store($this->app['config']['session.cookie'], $handler)这一句,Store也正是在这里被创建的。

了解FileSessionHandler

前面已经特别强调,那个反复提到的handler是FileSessionHandler,他实现了SessionHandlerInterface这个PHP接口。
回想起前面提到的readFromHandler这个函数,其中最关键的就是获取session数据这一句:$data = $this->handler->read($this->getId()),这个read,也正是调用了FileSessionHandler里的read函数

public function read($sessionId){    if ($this->files->exists($path = $this->path.'/'.$sessionId)) {        if (filemtime($path) >= Carbon::now()->subMinutes($this->minutes)->getTimestamp()) {            return $this->files->get($path, true);        }    }    return '';}

把那个$path打印出来会是下面这个样子,这也是数据存储的文件名(是不是觉得名字好怪?)。对照看一下,

"D:\wamp64\www\laravel\larablog\storage\framework/sessions/0z8Sd3lsnJYIkTBILQeamqh80floYcF0InWwdgm2"

该read函数最后返回的$this->files->get($path, true)正是文件里的内容,其中包括那个大名鼎鼎的CSRF-TOKEN。

a:4:{s:6:"_token";s:40:"zalGw4lffAeW6alsQKFBxb54QE9o3hIpEb0kK3Sd";s:9:"_previous";a:1:{s:3:"url";s:33:"http://localhost:8000/admin/login";}s:6:"_flash";a:2:{ s:3:"old";a:0:{}s:3:"new";a:0:{} }s:22:"PHPDEBUGBAR_STACK_DATA";a:0:{}}

这个数据再经过前面提到的readFromhandler的反序列化unserialize操作,会是下面这个样子

array:4 [▼  "_token" => "zalGw4lffAeW6alsQKFBxb54QE9o3hIpEb0kK3Sd"  "_previous" => array:1 [▼    "url" => "http://localhost:8000/admin/login"  ]  "_flash" => array:2 [▼    "old" => []    "new" => []  ]  "PHPDEBUGBAR_STACK_DATA" => []]

到这里,极为值得一提的是那个_token,你是不是在程序调试CSRF_TOKEN时经常碰到TokenMismatchException的问题?到这里查一下,说不定有意想不以的收获!
另外,关于这个CSRF-TOKEN,我们看到有两个地方,一个是在这里直接引入,如果这里没有,就会在Session->start()中用regenerateToken()创建。

至此,startSession完全运行完毕。
但还有些疑问,比如session是什么时候配置的?

在前面讲到的handle 函数中,在startSession之前,有这么一句配置函数
if ($this->sessionConfigured()) 。。。
这个sessionConfigured是这样的,

protected function sessionConfigured(){    return ! is_null($this->manager->getSessionConfig()['driver'] ?? null);}

该函数再调用sessionManager的getSessionConfig()来寻找配置,

直接dump($this)把sessionManager打印出来,看看这到底是个什么东西?(代码很长,所有有省略)

SessionManager {#193 ▼  app: Application {#2 ▶}  customCreators: []  drivers: array:1 [▼    file" => Store {#490 ▼      #id: "8bfH3MIt3iY5BiNUae14sCRd6QT1ScY2QwHdbBSE"      #name: "laravel_session"      #attributes: []      #handler: FileSessionHandler {#586 ▼        #files: Filesystem {#101}        #path: "D:\wamp64\www\laravel\larablog\storage\framework/sessions"        #minutes: "120"      }      #started: false    }  ]}

在sessionManger中加入调试代码,

public function getSessionConfig(){    dump($this);    dd();    return $this->app['config']['session'];}

得到下面的结果,

$this就是SessionManager,

    SessionManager {#193 ▼    app: Application {#2#basePath: "D:\wamp64\www\laravel\larablog"}    customCreators: []    drivers: []    }

$this->app[‘config’]是下面这个东西,

Repository {#24 ▼  items: array:14 [▼    "app" => array:13 [▶]    "auth" => array:4 [▶]    "broadcasting" => array:2 [▶]    "cache" => array:3 [▶]    "database" => array:4 [▶]    "debugbar" => array:13 [▶]    "filesystems" => array:3 [▶]    "mail" => array:9 [▶]    "queue" => array:3 [▶]    "scout" => array:5 [▶]    "services" => array:4 [▶]    "session" => array:15 [▶]    "view" => array:2 [▶]    "trustedproxy" => array:2 [▶]  ]}

要明白那个$this->app[‘config’]是如何得到Repository的,看下面:config 配置文件的加载。

插曲:config 配置文件的加载

config 配置文件由类 \Illuminate\Foundation\Bootstrap\LoadConfiguration::class 完成:
public function bootstrap(Application $app)

{    $items = [];    if (file_exists($cached = $app->getCachedConfigPath())) {        //注意,这个$cached返回的是路径,        //"D:\wamp64\www\laravel\larablog\bootstrap/cache/config.php"        $items = require $cached;        //items就相当于/bootstrap/cache/config.php里面的所有配置项        $loadedFromCache = true;    }    //下面,new Repository($items)创建一个仓库,并把所有配置项作为参数传递进去,    //然后绑定到$app->instances数组中的config上,说明config已经实例化。    $app->instance('config', $config = new Repository($items));    //此时,如果打印出$app,就会得到后面的那些内容,可以明显看到,    //instances数组中添加了’config’ (已经刻意展开了该项)    if (! isset($loadedFromCache)) {        $this->loadConfigurationFiles($app, $config);    }   $app->detectEnvironment(function () use ($config) {        return $config->get('app.env', 'production');    });    date_default_timezone_set($config->get('app.timezone', 'UTC'));    mb_internal_encoding('UTF-8');}

dd(#app)得到的结果,

Application {#2 ▼basePath: "D:\wamp64\www\laravel\larablog"  …instances: array:17 [▼…"config" => Repository {#24 ▼  #items: array:14 [▼    "app" => array:13 [▶]    "auth" => array:4 [▶]    "broadcasting" => array:2 [▶]    "cache" => array:3 [▶]    "database" => array:4 [▶]    "debugbar" => array:13 [▶]    "filesystems" => array:3 [▶]    "mail" => array:9 [▶]    "queue" => array:3 [▶]    "scout" => array:5 [▶]    "services" => array:4 [▶]    "session" => array:15 [▶]    "view" => array:2 [▶]    "trustedproxy" => array:2 [▶]  ]  …}

这个配置,也就是前面的getDefaultDriver()中拉出来用的配置。
当然,有兴趣的朋友可以继续研究loadConfigurationFiles。

至此,我们已经 全面了解这个session是如何启动的了。

原创粉丝点击