淺析 Laravel Session 元件的工作流程

blankqwq發表於2020-03-02

前言

寫框架的時候發現自己的session功能不是很完善,所有希望從laravel中學習一些精華!
當然Session的用處想必不用我在這裡敘述了

Session元件

t59wG6JfgQ.png!large

首先,我們需要知道Session元件主要由一下幾個部分組成,我將按照一下順序逐步解析Session元件

服務提供者(把元件註冊到容器等)
    Illuminate\Session\SessionServiceProvider
中介軟體
    Illuminate\Session\Middleware\StartSession.php
管理類
    Illuminate\Session\SessionManager
核心類
    Illuminate\Session\Store
    Illuminate\Session\EncryptedStore(加密繼承Store類)
處理類(實現SessionHandlerInterface介面)
    Illuminate\Session\NullSessionHandler
    Illuminate\Session\FileSessionHandler
    Illuminate\Session\DataBaseSessionHandler
    Illuminate\Session\CacheBasedSessionHandler
    Illuminate\Session\CookieSessionHandler
模板(stub這裡就不敘述了)
    Console\stubs\database.stub

SessionServiceProvider Session元件服務提供者

服務提供者註冊的過程為什麼會呼叫registerboot

public function register($provider, $options = [], $force = false)
{
  if (($registered = $this->getProvider($provider)) && ! $force) {
  return $registered;
 }
 if (is_string($provider)) {
  $provider = $this->resolveProvider($provider);
 }
 if (method_exists($provider, 'register')) {
 //呼叫register函式
  $provider->register();
 }
  $this->markAsRegistered($provider);

 if ($this->booted) {
 //呼叫boot函式
  $this->bootProvider($provider);
 }
  return $provider;
}

看完Application中處理provider的函式之後,我們再回到SessionServiceProvider

public function register()
{
    //註冊例項'session'=>new SessionManager($app)
  $this->registerSessionManager();
    //註冊session.store 實際上是呼叫SessionManager成員方法driver()
  $this->registerSessionDriver();
    //將StartSession::class注入容器
  $this->app->singleton(StartSession::class);
}

此時我們再進入SessionManager類的driver()函式中一探究竟
建立driver並存入陣列且返回
返回的是核心類Store但是內部的handler是根據配置檔案從容器等獲取的

public function driver($driver = null)
{
 //引數為空則獲取預設file
 $driver = $driver ?: $this->getDefaultDriver();
 //若本地無加入的driver則建立driver
 if (! isset($this->drivers[$driver])) {
      $this->drivers[$driver] = $this->createDriver($driver);
 }ch
 //返回建立的driver 以session.store為鍵返回建立的driver為值存入容器中
  return $this->drivers[$driver];
}

讓我們逐步開啟driver()方法


public function getDefaultDriver()
{
//返回配置檔案中的session.driver的session
  return $this->app['config']['session.driver'];
}
protected function createDriver($driver)
{
    //一般為空
     if (isset($this->customCreators[$driver])) {
         //呼叫customCreators[$driver]的方法生成driver
          return $this->callCustomCreator($driver);
     } else {
         //組合字串
          $method = 'create'.Str::studly($driver).'Driver';
     if (method_exists($this, $method)) {
         //呼叫方法建立driver
          return $this->$method();
     }
 }
 throw new InvalidArgumentException("Driver [$driver] not supported.");
}

若此時設定為redis 此時我們進入createRedisDriver方法

//建立redis驅動
protected function createRedisDriver()
{
    //建立CacheBasedSessionHandler物件
  $handler = $this->createCacheHandler('redis');
    //設定連結
  $handler->getCache()->getStore()->setConnection(
  $this->app['config']['session.connection']
 );
 /建立構建Store並且返回
  return $this->buildSession($handler);
}
//建立快取處理者
protected function createCacheHandler($driver)
{
    //若配置檔案中有就用配置檔案中,無就用傳入的$driver
  $store = $this->app['config']->get('session.store') ?: $driver;
    //例項化某SessionHandler
  return new CacheBasedSessionHandler(
  //這裡返回的是Illuminate\Cache\Repository物件內部store為RedisStore
  clone $this->app['cache']->store($store),
  //redis有效時間
  $this->app['config']['session.lifetime']
 );
}
//CacheManager的store方法 返回快取中的驅動
public function store($name = null)
{
  $name = $name ?: $this->getDefaultDriver();
    //返回並存入陣列
  return $this->stores[$name] = $this->get($name);
}

構建Session核心類(Store,EncryptedStore)物件的過程

protected function buildSession($handler)
{

 if ($this->app['config']['session.encrypt']) {

      return $this->buildEncryptedSession($handler);
 }
      return new Store($this->app['config']['session.cookie'], $handler);
}
//加密Store
return new EncryptedStore(
  $this->app['config']['session.cookie'], $handler, $this->app['encrypter']
);
//不加密Store
return new Store($this->app['config']['session.cookie'], $handler);

以上driver()方法就開啟完畢了,返回的物件存入陣列和容器中
Store,EncryptedStore例項化過程

public function __construct($name, SessionHandlerInterface $handler, $id = null)
{
    //生成sessionId
  $this->setId($id);
    //設定session的cookie名稱
  $this->name = $name;
    //設定處理者
  $this->handler = $handler;
}
public function __construct($name, SessionHandlerInterface $handler, EncrypterContract $encrypter, $id = null)
{
  $this->encrypter = $encrypter;
  parent::__construct($name, $handler, $id);
}

中介軟體引導Session過程

public function handle($request, Closure $next)
{
  $this->sessionHandled = true;

 if ($this->sessionConfigured()) {
     //將session儲存到request中
     $request->setLaravelSession(
         //返回session內容
        $session = $this->startSession($request)
     );
     //清理session中的垃圾
    $this->collectGarbage($session);
 }
     //呼叫下一層中介軟體/控制器
  $response = $next($request);

 if ($this->sessionConfigured()) {
     //儲存當前url
      $this->storeCurrentUrl($request, $session);
    //把cookie加入到response中
      $this->addCookieToResponse($response, $session);
 }
  return $response;
}

這裡session實際還是SessionManagerdriver函式返回的Store
這裡直接從SessionManager$this->drivers獲取即可

     $request->setLaravelSession(
        $session = $this->startSession($request)
     );

加密的實現

主要透過子類重寫父類的準備儲存和準備取出方法

//儲存的時候加密
    protected function prepareForStorage($data)
//反序列化的時候解密
    protected function prepareForUnserialize($data)

Session門臉

class Session extends Facade
{
      protected static function getFacadeAccessor()
     {  
         //從容器中取出的是SessionManager
        //至於為什麼自行去看Facade的 __callStatic魔術方法
         return 'session';
     }
 }
 //用Session門臉執行set("1","2");

Session::set("1","2");

//都透過__call
abstract class Manager{

    ...

    public function __call($method, $parameters)
    {
        //呼叫Store中的函式執行
      return $this->driver()->$method(...$parameters);
    }
}

//也就是執行Store中的成員方法set

看到這裡相比也學習到了laravel元件開發模式的益處和其中多種設計模式的使用使得框架的擴充套件性和穩定性增強

如果讓你構建一個框架並實現Session的功能你會怎麼做呢?

歡迎大家一起討論

本作品採用《CC 協議》,轉載必須註明作者和本文連結
學習,沖沖衝~

相關文章