說明:本文主要經過學習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_session
。redis
開發環境:Laravel5.3 + PHP7
cookie
首先看下\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的增刪改查操做,到時見。