Laravel5.3之Session源碼解析(上)

說明:本文主要經過學習Laravel的session源碼學習Laravel是如何設計session的,將本身的學習心得分享出來,但願對別人有所幫助。Laravel在web middleware中定義了session中間件\Illuminate\Session\Middleware\StartSession::class,並經過該中間件來設計session,這個中間件的主要工做分爲三步:php

(1)啓動session,經過session handler從一些存儲介質如redis中讀取session值;laravel

(2)操做session,對session數據CRUD增刪改查操做;web

(3)關閉session,把session_id寫入到response header中,默認是laravel_sessionredis

開發環境:Laravel5.3 + PHP7cookie

啓動Session

首先看下\Illuminate\Session\Middleware\StartSession::class中間件源碼中handle()方法:session

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()的源碼:app

/** * 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()。首先看下第一步的源碼:ui

// \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()方法:this

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的增刪改查操做,到時見。

相關文章
相關標籤/搜索