Laravel框架核心內(nèi)容:Session源碼的詳細(xì)分析

本篇文章給大家?guī)淼膬?nèi)容是關(guān)于laravel框架核心內(nèi)容:session源碼的詳細(xì)分析 ,有一定的參考價(jià)值,有需要的朋友可以參考一下,希望對(duì)你有所幫助。

Session 模塊源碼解析

由于HTTP最初是一個(gè)匿名、無狀態(tài)的請(qǐng)求/響應(yīng)協(xié)議,服務(wù)器處理來自客戶端的請(qǐng)求然后向客戶端回送一條響應(yīng)。現(xiàn)代Web應(yīng)用程序?yàn)榱私o用戶提供個(gè)性化的服務(wù)往往需要在請(qǐng)求中識(shí)別出用戶或者在用戶的多條請(qǐng)求之間共享數(shù)據(jù)。Session 提供了一種在多個(gè)請(qǐng)求之間存儲(chǔ)、共享有關(guān)用戶的信息的方法。laravel 通過同一個(gè)可讀性強(qiáng)的 API 處理各種自帶的 Session 后臺(tái)驅(qū)動(dòng)程序。

Session支持的驅(qū)動(dòng):

  • file – 將 Session 保存在 storage/framework/sessions 中。

  • cookie – Session 保存在安全加密的 Cookie 中。

  • database – Session 保存在關(guān)系型數(shù)據(jù)庫中。

  • memcached / redis – Sessions 保存在其中一個(gè)快速且基于緩存的存儲(chǔ)系統(tǒng)中。

  • array – Sessions 保存在 PHP 數(shù)組中,不會(huì)被持久化。

這篇文章我們來詳細(xì)的看一下Laravel中Session服務(wù)的實(shí)現(xiàn)原理,Session服務(wù)有哪些部分組成以及每部分的角色、它是何時(shí)被注冊(cè)到服務(wù)容器的、請(qǐng)求是在何時(shí)啟用session的以及如何為session擴(kuò)展驅(qū)動(dòng)。

注冊(cè)Session服務(wù)

在之前的很多文章里都提到過,服務(wù)是通過服務(wù)提供器注冊(cè)到服務(wù)容器里的,Laravel在啟動(dòng)階段會(huì)依次執(zhí)行config/app.php里providers數(shù)組里的服務(wù)提供器register方法來注冊(cè)框架需要的服務(wù),所以我們很容易想到session服務(wù)也是在這個(gè)階段被注冊(cè)到服務(wù)容器里的。

'providers' => [      /*      * Laravel Framework Service Providers...      */     ......     IlluminateSessionSessionServiceProvider::class     ...... ],

果真在providers里確實(shí)有SessionServiceProvider 我們看一下它的源碼,看看session服務(wù)的注冊(cè)細(xì)節(jié)

namespace IlluminateSession;  use IlluminateSupportServiceProvider; use IlluminateSessionMiddlewareStartSession;  class SessionServiceProvider extends ServiceProvider {     /**      * Register the service provider.      *      * @return void      */     public function register()     {         $this->registerSessionManager();          $this->registerSessionDriver();          $this->app->singleton(StartSession::class);     }      /**      * Register the session manager instance.      *      * @return void      */     protected function registerSessionManager()     {         $this->app->singleton('session', function ($app) {             return new SessionManager($app);         });     }      /**      * Register the session driver instance.      *      * @return void      */     protected function registerSessionDriver()     {         $this->app->singleton('session.store', function ($app) {             // First, we will create the session manager which is responsible for the             // creation of the various session drivers when they are needed by the             // application instance, and will resolve them on a lazy load basis.             return $app->make('session')->driver();         });     } }

在SessionServiceProvider中一共注冊(cè)了三個(gè)服務(wù):

  • session服務(wù),session服務(wù)解析出來后是一個(gè)SessionManager對(duì)象,它的作用是創(chuàng)建session驅(qū)動(dòng)器并且在需要時(shí)解析出驅(qū)動(dòng)器(延遲加載),此外一切訪問、更新session數(shù)據(jù)的方法調(diào)用都是由它代理給對(duì)應(yīng)的session驅(qū)動(dòng)器來實(shí)現(xiàn)的。

  • session.store ?Session驅(qū)動(dòng)器,IlluminateSessionStore的實(shí)例,Store類實(shí)現(xiàn)了IlluminateContractsSessionSession契約向開發(fā)者提供了統(tǒng)一的接口來訪問Session數(shù)據(jù),驅(qū)動(dòng)器通過不同的SessionHandler來訪問database、redis、memcache等不同的存儲(chǔ)介質(zhì)里的session數(shù)據(jù)。

  • StartSession::class 中間件,提供了在請(qǐng)求開始時(shí)打開Session,響應(yīng)發(fā)送給客戶端前將session標(biāo)示符寫入到Cookie中,此外作為一個(gè)terminate中間件在響應(yīng)發(fā)送給客戶端后它在terminate()方法中會(huì)將請(qǐng)求中對(duì)session數(shù)據(jù)的更新保存到存儲(chǔ)介質(zhì)中去。

創(chuàng)建Session驅(qū)動(dòng)器

上面已經(jīng)說了SessionManager是用來創(chuàng)建session驅(qū)動(dòng)器的,它里面定義了各種個(gè)樣的驅(qū)動(dòng)器創(chuàng)建器(創(chuàng)建驅(qū)動(dòng)器實(shí)例的方法) 通過它的源碼來看一下session驅(qū)動(dòng)器是證明被創(chuàng)建出來的:

<?php  namespace IlluminateSession;  use IlluminateSupportManager;  class SessionManager extends Manager {     /**      * 調(diào)用自定義驅(qū)動(dòng)創(chuàng)建器 (通過Session::extend注冊(cè)的)      *      * @param  string  $driver      * @return mixed      */     protected function callCustomCreator($driver)     {         return $this->buildSession(parent::callCustomCreator($driver));     }      /**      * 創(chuàng)建數(shù)組類型的session驅(qū)動(dòng)器(不會(huì)持久化)      *      * @return IlluminateSessionStore      */     protected function createArrayDriver()     {         return $this->buildSession(new NullSessionHandler);     }      /**      * 創(chuàng)建Cookie session驅(qū)動(dòng)器      *      * @return IlluminateSessionStore      */     protected function createCookieDriver()     {         return $this->buildSession(new CookieSessionHandler(             $this->app['cookie'], $this->app['config']['session.lifetime']         ));     }      /**      * 創(chuàng)建文件session驅(qū)動(dòng)器      *      * @return IlluminateSessionStore      */     protected function createFileDriver()     {         return $this->createNativeDriver();     }      /**      * 創(chuàng)建文件session驅(qū)動(dòng)器      *      * @return IlluminateSessionStore      */     protected function createNativeDriver()     {         $lifetime = $this->app['config']['session.lifetime'];          return $this->buildSession(new FileSessionHandler(             $this->app['files'], $this->app['config']['session.files'], $lifetime         ));     }      /**      * 創(chuàng)建Database型的session驅(qū)動(dòng)器      *      * @return IlluminateSessionStore      */     protected function createDatabaseDriver()     {         $table = $this->app['config']['session.table'];          $lifetime = $this->app['config']['session.lifetime'];          return $this->buildSession(new DatabaseSessionHandler(             $this->getDatabaseConnection(), $table, $lifetime, $this->app         ));     }      /**      * Get the database connection for the database driver.      *      * @return IlluminateDatabaseConnection      */     protected function getDatabaseConnection()     {         $connection = $this->app['config']['session.connection'];          return $this->app['db']->connection($connection);     }      /**      * Create an instance of the APC session driver.      *      * @return IlluminateSessionStore      */     protected function createApcDriver()     {         return $this->createCacheBased('apc');     }      /**      * 創(chuàng)建memcache session驅(qū)動(dòng)器      *      * @return IlluminateSessionStore      */     protected function createMemcachedDriver()     {         return $this->createCacheBased('memcached');     }      /**      * 創(chuàng)建redis session驅(qū)動(dòng)器      *      * @return IlluminateSessionStore      */     protected function createRedisDriver()     {         $handler = $this->createCacheHandler('redis');          $handler->getCache()->getStore()->setConnection(             $this->app['config']['session.connection']         );          return $this->buildSession($handler);     }      /**      * 創(chuàng)建基于Cache的session驅(qū)動(dòng)器 (創(chuàng)建memcache、apc驅(qū)動(dòng)器時(shí)都會(huì)調(diào)用這個(gè)方法)      *      * @param  string  $driver      * @return IlluminateSessionStore      */     protected function createCacheBased($driver)     {         return $this->buildSession($this->createCacheHandler($driver));     }      /**      * 創(chuàng)建基于Cache的session handler      *      * @param  string  $driver      * @return IlluminateSessionCacheBasedSessionHandler      */     protected function createCacheHandler($driver)     {         $store = $this->app['config']->get('session.store') ?: $driver;          return new CacheBasedSessionHandler(             clone $this->app['cache']->store($store),             $this->app['config']['session.lifetime']         );     }      /**      * 構(gòu)建session驅(qū)動(dòng)器      *      * @param  SessionHandlerInterface  $handler      * @return IlluminateSessionStore      */     protected function buildSession($handler)     {         if ($this->app['config']['session.encrypt']) {             return $this->buildEncryptedSession($handler);         }          return new Store($this->app['config']['session.cookie'], $handler);     }      /**      * 構(gòu)建加密的Session驅(qū)動(dòng)器      *      * @param  SessionHandlerInterface  $handler      * @return IlluminateSessionEncryptedStore      */     protected function buildEncryptedSession($handler)     {         return new EncryptedStore(             $this->app['config']['session.cookie'], $handler, $this->app['encrypter']         );     }      /**      * 獲取config/session.php里的配置      *      * @return array      */     public function getSessionConfig()     {         return $this->app['config']['session'];     }      /**      * 獲取配置里的session驅(qū)動(dòng)器名稱      *      * @return string      */     public function getDefaultDriver()     {         return $this->app['config']['session.driver'];     }      /**      * 設(shè)置配置里的session名稱      *      * @param  string  $name      * @return void      */     public function setDefaultDriver($name)     {         $this->app['config']['session.driver'] = $name;     } }

通過SessionManager的源碼可以看到驅(qū)動(dòng)器對(duì)外提供了統(tǒng)一的訪問接口,而不同類型的驅(qū)動(dòng)器之所以能訪問不同的存儲(chǔ)介質(zhì)是驅(qū)動(dòng)器是通過SessionHandler來訪問存儲(chǔ)介質(zhì)里的數(shù)據(jù)的,而不同的SessionHandler統(tǒng)一都實(shí)現(xiàn)了PHP內(nèi)建的SessionHandlerInterface接口,所以驅(qū)動(dòng)器能夠通過統(tǒng)一的接口方法訪問到不同的session存儲(chǔ)介質(zhì)里的數(shù)據(jù)。

驅(qū)動(dòng)器訪問Session 數(shù)據(jù)

開發(fā)者使用Session門面或者$request->session()訪問Session數(shù)據(jù)都是通過session服務(wù)即SessionManager對(duì)象轉(zhuǎn)發(fā)給對(duì)應(yīng)的驅(qū)動(dòng)器方法的,在IlluminateSessionStore的源碼中我們也能夠看到Laravel里用到的session方法都定義在這里。

Session::get($key); Session::has($key); Session::put($key, $value); Session::pull($key); Session::flash($key, $value); Session::forget($key);

上面這些session方法都能在IlluminateSessionStore類里找到具體的方法實(shí)現(xiàn)

<?php  namespace IlluminateSession;  use Closure; use IlluminateSupportArr; use IlluminateSupportStr; use SessionHandlerInterface; use IlluminateContractsSessionSession;  class Store implements Session {     /**      * The session ID.      *      * @var string      */     protected $id;      /**      * The session name.      *      * @var string      */     protected $name;      /**      * The session attributes.      *      * @var array      */     protected $attributes = [];      /**      * The session handler implementation.      *      * @var SessionHandlerInterface      */     protected $handler;      /**      * Session store started status.      *      * @var bool      */     protected $started = false;      /**      * Create a new session instance.      *      * @param  string $name      * @param  SessionHandlerInterface $handler      * @param  string|null $id      * @return void      */     public function __construct($name, SessionHandlerInterface $handler, $id = null)     {         $this->setId($id);         $this->name = $name;         $this->handler = $handler;     }      /**      * 開啟session, 通過session handler從存儲(chǔ)介質(zhì)中讀出數(shù)據(jù)暫存在attributes屬性里      *      * @return bool      */     public function start()     {         $this->loadSession();          if (! $this->has('_token')) {             $this->regenerateToken();         }          return $this->started = true;     }      /**      * 通過session handler從存儲(chǔ)中加載session數(shù)據(jù)暫存到attributes屬性里      *      * @return void      */     protected function loadSession()     {         $this->attributes = array_merge($this->attributes, $this->readFromHandler());     }      /**      * 通過handler從存儲(chǔ)中讀出session數(shù)據(jù)      *      * @return array      */     protected function readFromHandler()     {         if ($data = $this->handler->read($this->getId())) {             $data = @unserialize($this->prepareForUnserialize($data));              if ($data !== false && ! is_null($data) && is_array($data)) {                 return $data;             }         }          return [];     }      /**      * Prepare the raw string data from the session for unserialization.      *      * @param  string  $data      * @return string      */     protected function prepareForUnserialize($data)     {         return $data;     }      /**      * 將session數(shù)據(jù)保存到存儲(chǔ)中      *      * @return bool      */     public function save()     {         $this->ageFlashData();          $this->handler->write($this->getId(), $this->prepareForStorage(             serialize($this->attributes)         ));          $this->started = false;     }      /**      * Checks if a key is present and not null.      *      * @param  string|array  $key      * @return bool      */     public function has($key)     {         return ! collect(is_array($key) ? $key : func_get_args())->contains(function ($key) {             return is_null($this->get($key));         });     }      /**      * Get an item from the session.      *      * @param  string  $key      * @param  mixed  $default      * @return mixed      */     public function get($key, $default = null)     {         return Arr::get($this->attributes, $key, $default);     }      /**      * Get the value of a given key and then forget it.      *      * @param  string  $key      * @param  string  $default      * @return mixed      */     public function pull($key, $default = null)     {         return Arr::pull($this->attributes, $key, $default);     }      /**      * Put a key / value pair or array of key / value pairs in the session.      *      * @param  string|array  $key      * @param  mixed       $value      * @return void      */     public function put($key, $value = null)     {         if (! is_array($key)) {             $key = [$key => $value];         }          foreach ($key as $arrayKey => $arrayValue) {             Arr::set($this->attributes, $arrayKey, $arrayValue);         }     }      /**      * Flash a key / value pair to the session.      *      * @param  string  $key      * @param  mixed   $value      * @return void      */     public function flash(string $key, $value = true)     {         $this->put($key, $value);          $this->push('_flash.new', $key);          $this->removeFromOldFlashData([$key]);     }      /**      * Remove one or many items from the session.      *      * @param  string|array  $keys      * @return void      */     public function forget($keys)     {         Arr::forget($this->attributes, $keys);     }      /**      * Remove all of the items from the session.      *      * @return void      */     public function flush()     {         $this->attributes = [];     }       /**      * Determine if the session has been started.      *      * @return bool      */     public function isStarted()     {         return $this->started;     }      /**      * Get the name of the session.      *      * @return string      */     public function getName()     {         return $this->name;     }      /**      * Set the name of the session.      *      * @param  string  $name      * @return void      */     public function setName($name)     {         $this->name = $name;     }      /**      * Get the current session ID.      *      * @return string      */     public function getId()     {         return $this->id;     }      /**      * Set the session ID.      *      * @param  string  $id      * @return void      */     public function setId($id)     {         $this->id = $this->isValidId($id) ? $id : $this->generateSessionId();     }      /**      * Determine if this is a valid session ID.      *      * @param  string  $id      * @return bool      */     public function isValidId($id)     {         return is_string($id) && ctype_alnum($id) && strlen($id) === 40;     }      /**      * Get a new, random session ID.      *      * @return string      */     protected function generateSessionId()     {         return Str::random(40);     }      /**      * Set the existence of the session on the handler if applicable.      *      * @param  bool  $value      * @return void      */     public function setExists($value)     {         if ($this->handler instanceof ExistenceAwareInterface) {             $this->handler->setExists($value);         }     }      /**      * Get the CSRF token value.      *      * @return string      */     public function token()     {         return $this->get('_token');     }          /**      * Regenerate the CSRF token value.      *      * @return void      */     public function regenerateToken()     {         $this->put('_token', Str::random(40));     } }

由于驅(qū)動(dòng)器的源碼比較多,我只留下一些常用和方法,并對(duì)關(guān)鍵的方法做了注解,完整源碼可以去看IlluminateSessionStore類的源碼。 通過Store類的源碼我們可以發(fā)現(xiàn):

  • 每個(gè)session數(shù)據(jù)里都會(huì)有一個(gè)_token數(shù)據(jù)來做CSRF防范。

  • Session開啟后會(huì)將session數(shù)據(jù)從存儲(chǔ)中讀出暫存到attributes屬性。

  • 驅(qū)動(dòng)器提供給應(yīng)用操作session數(shù)據(jù)的方法都是直接操作的attributes屬性里的數(shù)據(jù)。

同時(shí)也會(huì)產(chǎn)生一些疑問,在平時(shí)開發(fā)時(shí)我們并沒有主動(dòng)的去開啟和保存session,數(shù)據(jù)是怎么加載和持久化的?通過session在用戶的請(qǐng)求間共享數(shù)據(jù)是需要在客戶端cookie存儲(chǔ)一個(gè)session id的,這個(gè)cookie又是在哪里設(shè)置的?

上面的兩個(gè)問題給出的解決方案是最開始說的第三個(gè)服務(wù)StartSession中間件

StartSession 中間件

<?php  namespace IlluminateSessionMiddleware;  use Closure; use IlluminateHttpRequest; use IlluminateSupportCarbon; use IlluminateSessionSessionManager; use IlluminateContractsSessionSession; use IlluminateSessionCookieSessionHandler; use SymfonyComponentHttpFoundationCookie; use SymfonyComponentHttpFoundationResponse;  class StartSession {     /**      * The session manager.      *      * @var IlluminateSessionSessionManager      */     protected $manager;      /**      * Indicates if the session was handled for the current request.      *      * @var bool      */     protected $sessionHandled = false;      /**      * Create a new session middleware.      *      * @param  IlluminateSessionSessionManager  $manager      * @return void      */     public function __construct(SessionManager $manager)     {         $this->manager = $manager;     }      /**      * Handle an incoming request.      *      * @param  IlluminateHttpRequest  $request      * @param  Closure  $next      * @return mixed      */     public function handle($request, Closure $next)     {         $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;     }      /**      * Perform any final actions for the request lifecycle.      *      * @param  IlluminateHttpRequest  $request      * @param  SymfonyComponentHttpFoundationResponse  $response      * @return void      */     public function terminate($request, $response)     {         if ($this->sessionHandled && $this->sessionConfigured() && ! $this->usingCookieSessions()) {             $this->manager->driver()->save();         }     }      /**      * Start the session for the given request.      *      * @param  IlluminateHttpRequest  $request      * @return IlluminateContractsSessionSession      */     protected function startSession(Request $request)     {         return tap($this->getSession($request), function ($session) use ($request) {             $session->setRequestOnHandler($request);              $session->start();         });     }      /**      * Add the session cookie to the application response.      *      * @param  SymfonyComponentHttpFoundationResponse  $response      * @param  IlluminateContractsSessionSession  $session      * @return void      */     protected function addCookieToResponse(Response $response, Session $session)     {         if ($this->usingCookieSessions()) {             //將session數(shù)據(jù)保存到cookie中,cookie名是本條session數(shù)據(jù)的ID標(biāo)識(shí)符             $this->manager->driver()->save();         }          if ($this->sessionIsPersistent($config = $this->manager->getSessionConfig())) {            //將本條session的ID標(biāo)識(shí)符保存到cookie中,cookie名是session配置文件里設(shè)置的cookie名             $response->headers->setCookie(new Cookie(                 $session->getName(), $session->getId(), $this->getCookieExpirationDate(),                 $config['path'], $config['domain'], $config['secure'] ?? false,                 $config['http_only'] ?? true, false, $config['same_site'] ?? null             ));         }     }       /**      * Determine if the configured session driver is persistent.      *      * @param  array|null  $config      * @return bool      */     protected function sessionIsPersistent(array $config = null)     {         $config = $config ?: $this->manager->getSessionConfig();          return ! in_array($config['driver'], [null, 'array']);     }      /**      * Determine if the session is using cookie sessions.      *      * @return bool      */     protected function usingCookieSessions()     {         if ($this->sessionConfigured()) {             return $this->manager->driver()->getHandler() instanceof CookieSessionHandler;         }          return false;     } }

同樣的我只保留了最關(guān)鍵的代碼,可以看到中間件在請(qǐng)求進(jìn)來時(shí)會(huì)先進(jìn)行session start操作,然后在響應(yīng)返回給客戶端前將session id 設(shè)置到了cookie響應(yīng)頭里面, cookie的名稱是由config/session.php里的cookie配置項(xiàng)設(shè)置的,值是本條session的ID標(biāo)識(shí)符。與此同時(shí)如果session驅(qū)動(dòng)器用的是CookieSessionHandler還會(huì)將session數(shù)據(jù)保存到cookie里cookie的名字是本條session的ID標(biāo)示符(呃, 有點(diǎn)繞,其實(shí)就是把存在redis里的那些session數(shù)據(jù)以ID為cookie名存到cookie里了, 值是JSON格式化的session數(shù)據(jù))。

最后在響應(yīng)發(fā)送完后,在terminate方法里會(huì)判斷驅(qū)動(dòng)器用的如果不是CookieSessionHandler,那么就調(diào)用一次$this->manager->driver()->save();將session數(shù)據(jù)持久化到存儲(chǔ)中 (我現(xiàn)在還沒有搞清楚為什么不統(tǒng)一在這里進(jìn)行持久化,可能看完Cookie服務(wù)的源碼就清楚了)。

添加自定義驅(qū)動(dòng)

關(guān)于添加自定義驅(qū)動(dòng),官方文檔給出了一個(gè)例子,MongoHandler必須實(shí)現(xiàn)統(tǒng)一的SessionHandlerInterface接口里的方法:

<?php  namespace AppExtensions;  class MongoHandler implements SessionHandlerInterface {     public function open($savePath, $sessionName) {}     public function close() {}     public function read($sessionId) {}     public function write($sessionId, $data) {}     public function destroy($sessionId) {}     public function gc($lifetime) {} }

定義完驅(qū)動(dòng)后在AppServiceProvider里注冊(cè)一下:

<?php  namespace AppProviders;  use AppExtensionsMongoSessionStore; use IlluminateSupportFacadesSession; use IlluminateSupportServiceProvider;  class SessionServiceProvider extends ServiceProvider {     /**      * 執(zhí)行注冊(cè)后引導(dǎo)服務(wù)。      *      * @return void      */     public function boot()     {         Session::extend('mongo', function ($app) {             // Return implementation of SessionHandlerInterface...             return new MongoSessionStore;         });     } }

這樣在用SessionManager的driver方法創(chuàng)建mongo類型的驅(qū)動(dòng)器的時(shí)候就會(huì)調(diào)用callCustomCreator方法去創(chuàng)建mongo類型的Session驅(qū)動(dòng)器了。

相關(guān)推薦:

如何使用Larave制定一個(gè)MySQL數(shù)據(jù)庫備份計(jì)劃任務(wù)

如何使用Larave制定一個(gè)MySQL數(shù)據(jù)庫備份計(jì)劃任務(wù)

如何使用Larave制定一個(gè)MySQL數(shù)據(jù)庫備份計(jì)劃任務(wù)

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊15 分享