本篇文章給大家?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ù)