Laravel
是徹底廢棄了 PHP
官方提供的 Session
服務而本身實現了。php
實現機參考文末拓展。web
咱們從路由調用控制器的代碼來反推比較好理解!redis
定位到【Laravel-海賊王系列】第十三章,路由&控制器解析的代碼api
// "這段就是路由調用控制器的地方"
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
// "這裏的 `$middleware` 就有關於 `Session` 啓動的中間件"
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
複製代碼
這裏經過 gatherRouteMiddleware($route)
這個方法來獲取中間件了bash
public function gatherRouteMiddleware(Route $route)
{
$middleware = collect($route->gatherMiddleware())->map(function ($name) {
return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
})->flatten();
return $this->sortMiddleware($middleware);
}
複製代碼
上面的代碼咱們分幾步來拆解:cookie
$route->gatherMiddleware()
public function gatherMiddleware()
{
if (! is_null($this->computedMiddleware)) {
return $this->computedMiddleware;
}
return $this->computedMiddleware = array_unique(array_merge(
$this->middleware(), $this->controllerMiddleware()
), SORT_REGULAR);
}
複製代碼
這裏主要看 $this->middleware()
返回值session
public function middleware($middleware = null)
{
if (is_null($middleware)) {
return (array) ($this->action['middleware'] ?? []);
}
if (is_string($middleware)) {
$middleware = func_get_args();
}
$this->action['middleware'] = array_merge(
(array) ($this->action['middleware'] ?? []), $middleware
);
return $this;
}
複製代碼
下圖就是 Illuminate\Routing\Route
的 $this->action
屬性 app
咱們從中解析出 web
字符串返回。框架
接着看 return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
函數
這段代碼主要功能就是從 $this->middleware
和 $this->middlewareGroups
中解析出 $name
對應的中間件。
咱們上面解析的 web
字符串就是傳遞到這裏的 $name
那麼 $this->middleware
和 $this->middlewareGroups
是什麼?咱們先看圖再分析怎麼來的!
這兩個屬性是在內核的構造函數注入的 App\Http\Kernel
繼承了 Illuminate\Foundation\Http\Kernel
在 index.php
中加載的真實內核類
// "這個類沒有構造函數,因此執行了父類的構造函數。"
// "排序用的中間件組"
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
// "不一樣請求類型的中間件組"
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
// "通用中間件組"
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
複製代碼
Illuminate\Foundation\Http\Kernel
內核構造函數
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;
$router->middlewarePriority = $this->middlewarePriority; // 注入到了 Router 對象的對應成員中
foreach ($this->middlewareGroups as $key => $middleware) {
$router->middlewareGroup($key, $middleware); // 注入到了 Router 對象的對應成員中
}
foreach ($this->routeMiddleware as $key => $middleware) {
$router->aliasMiddleware($key, $middleware); // 注入到了 Router 對象的對應成員中
}
}
複製代碼
返回值以下圖
return $this->sortMiddleware($middleware);
protected function sortMiddleware(Collection $middlewares)
{
return (new SortedMiddleware($this->middlewarePriority, $middlewares))->all();
}
複製代碼
這就是按照上面解析的 $this->middlewarePriority
的優先級進行排序。
StartSession
上一步能夠看到在 web
請求下咱們是會默認經過 StartSession
中間件的。
咱們先看看整個類都有什麼,爲了閱讀體驗隱藏一些非重要的方法。
<?php
namespace Illuminate\Session\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Session\SessionManager;
use Illuminate\Contracts\Session\Session;
use Illuminate\Session\CookieSessionHandler;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Response;
class StartSession
{
protected $sessionHandled = false;
public function __construct(SessionManager $manager)
{
// "經過 SessionManager 來管理驅動,方便支持多種形式存儲"
$this->manager = $manager;
}
public function handle($request, Closure $next)
{
$this->sessionHandled = true;
if ($this->sessionConfigured()) {
$request->setLaravelSession(
$session = $this->startSession($request)
);
$this->collectGarbage($session);
}
$response = $next($request);
if ($this->sessionConfigured()) {
$this->storeCurrentUrl($request, $session);
$this->addCookieToResponse($response, $session);
}
return $response;
}
public function terminate($request, $response)
{
if ($this->sessionHandled && $this->sessionConfigured() && ! $this->usingCookieSessions()) {
$this->manager->driver()->save();
}
}
protected function startSession(Request $request)
{
return tap($this->getSession($request), function ($session) use ($request) {
$session->setRequestOnHandler($request);
$session->start();
});
}
public function getSession(Request $request)
{
return tap($this->manager->driver(), function ($session) use ($request) {
$session->setId($request->cookies->get($session->getName()));
});
}
protected function collectGarbage(Session $session){ ... }
protected function configHitsLottery(array $config){ ... }
protected function storeCurrentUrl(Request $request, $session){ ... }
protected function addCookieToResponse(Response $response, Session $session){ ... }
protected function getSessionLifetimeInSeconds()
{
return ($this->manager->getSessionConfig()['lifetime'] ?? null) * 60;
}
protected function getCookieExpirationDate(){ ... }
protected function sessionConfigured()
{
return ! is_null($this->manager->getSessionConfig()['driver'] ?? null);
}
protected function sessionIsPersistent(array $config = null){ ... }
protected function usingCookieSessions(){ ... }
}
複製代碼
這是整個 StartSession
的中間件
public function __construct(SessionManager $manager)
{
// "經過 Illuminate\Session\SessionManager 來管理驅動,方便支持多種形式存儲"
$this->manager = $manager;
}
複製代碼
session
實例接着看中間件的 handle()
方法,核心就是獲取 session
對象而後設置到 $request
對象中
public function handle($request, Closure $next)
{
$this->sessionHandled = true;
// "經過 config('session.driver'), 框架默認是 'file'"
if ($this->sessionConfigured()) {
$request->setLaravelSession(
$session = $this->startSession($request)
);
$this->collectGarbage($session);
}
$response = $next($request);
if ($this->sessionConfigured()) {
$this->storeCurrentUrl($request, $session);
$this->addCookieToResponse($response, $session);
}
return $response;
}
複製代碼
咱們先經過 $request->setLaravelSession($session = $this->startSession($request) );
獲取一個 session
對象
追蹤代碼 $this->startSession($request)
protected function startSession(Request $request)
{
return tap($this->getSession($request), function ($session) use ($request) {
$session->setRequestOnHandler($request);
$session->start();
});
}
複製代碼
繼續追蹤 $this->getSession($request)
public function getSession(Request $request)
{
return tap($this->manager->driver(), function ($session) use ($request) {
$session->setId($request->cookies->get($session->getName()));
});
}
複製代碼
這裏要追蹤 $this->manager->driver()
返回的是什麼對象!
咱們直接調用了 Illuminate\Support\Manager
這個抽象類的 driver
方法
public function driver($driver = null)
{
$driver = $driver ?: $this->getDefaultDriver();
if (is_null($driver)) {
throw new InvalidArgumentException(sprintf(
'Unable to resolve NULL driver for [%s].', static::class
));
}
if (! isset($this->drivers[$driver])) {
$this->drivers[$driver] = $this->createDriver($driver);
}
return $this->drivers[$driver];
}
複製代碼
這裏只須要關注
protected function createDriver($driver)
{
if (isset($this->customCreators[$driver])) {
return $this->callCustomCreator($driver);
} else {
$method = 'create'.Str::studly($driver).'Driver';
if (method_exists($this, $method)) {
return $this->$method();
}
}
throw new InvalidArgumentException("Driver [$driver] not supported.");
}
複製代碼
到了這裏其實就是獲得一個 $method
方法那麼框架其實最後調用了 createFileDriver()
這裏其實就是工廠模式根據配置來加載對應驅動,即便更換 redis
驅動只不過變成 createRedisDriver()
而已。
回到一開始構造函數注入的 Illuminate\Session\SessionManager
對象
protected function createFileDriver()
{
return $this->createNativeDriver();
}
複製代碼
繼續展開
protected function createNativeDriver()
{
$lifetime = $this->app['config']['session.lifetime'];
return $this->buildSession(new FileSessionHandler(
$this->app['files'], $this->app['config']['session.files'], $lifetime
));
}
複製代碼
那麼實際最後獲取一個 Illuminate\Session\FileSessionHandler
對象
咱們總算獲得了直接和存儲層交互的驅動
展開結構
<?php
namespace Illuminate\Session;
use SessionHandlerInterface;
use Illuminate\Support\Carbon;
use Symfony\Component\Finder\Finder;
use Illuminate\Filesystem\Filesystem;
class FileSessionHandler implements SessionHandlerInterface
{
protected $files;
protected $path;
protected $minutes;
public function __construct(Filesystem $files, $path, $minutes)
{
$this->path = $path;
$this->files = $files;
$this->minutes = $minutes;
}
// "爲了閱讀體驗就不展開裏面的代碼,實際功能就是調用存儲層進行增刪改查"
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){ ... }
}
複製代碼
最後一段代碼
protected function buildSession($handler)
{
if ($this->app['config']['session.encrypt']) {
return $this->buildEncryptedSession($handler);
}
return new Store($this->app['config']['session.cookie'], $handler);
}
複製代碼
最後根據加密配置返回一個 Illuminate\Session\EncryptedStore
或者 Illuminate\Session\Store
對象
這個 Store
咱們看看構造函數就會了解他的功能!
public function __construct($name, SessionHandlerInterface $handler, $id = null)
{
$this->setId($id);
$this->name = $name;
$this->handler = $handler;
}
複製代碼
這個接收了 SessionHandler
就至關於擁有了和數據存儲交互的能力,這個類對用戶層提供了
和session
交互的全部 api
,對用戶來講隱藏了底層的驅動實現。
好了,回到開始的部分
protected function startSession(Request $request)
{
return tap($this->getSession($request), function ($session) use ($request) {
$session->setRequestOnHandler($request);
$session->start();
});
}
複製代碼
咱們已經知道 $session
這個對象就是 Illuminate\Session\Store
接着就是調用 setRequestOnHandler()
和 start()
方法
這裏咱們無論 setRequestOnHandler()
由於這段代碼是在針對使用 Cookie
來當驅動的時候設定的,基本沒用。
直接看 start()
方法
public function start()
{
$this->loadSession();
if (! $this->has('_token')) {
$this->regenerateToken();
}
return $this->started = true;
}
複製代碼
繼續看
protected function loadSession()
{
$this->attributes = array_merge($this->attributes, $this->readFromHandler());
}
複製代碼
繼續看
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 [];
}
複製代碼
這裏的代碼就是直接經過驅動傳入 SessionId
而後獲取存入的數據
以後賦值給 Illuminate\Session\Store
的 $this->attributes
所以 Illuminate\Session\Store
對象纔是真正和咱們打交道的對象!
Store
經過上面的分析,我麼知道 Laravel
屏蔽了數據驅動層,直接向上層
提供了 Store
對象來實現對整個 Session
的調用,用戶不須要再關心
底層的實現邏輯,只須要按照配置設定好驅動而後調用 Store
中提供的方法便可!
最後咱們全部的 get()
set()
flush()
等等操做只不過是 Store
提供的服務。
SessionHandlerInterface
關於實現 implements SessionHandlerInterface
其實 PHP
的針對自定義 Session
提供了預留接口,要本身拓展就必須實現這個接口中定義的方法,
在 PHP
底層會經過這幾個方法將 SessionID
傳遞進來。
經過本章咱們要了解幾個重點
StartSession
中間件的啓動過程 ( Kernel
中配置)Session
驅動的加載方式 (經過 SessionManager
工廠加載)Session
的全部操做是由 Illuminate\Session\Store
對象提供PHP
提供 SessionHandlerInterface
來拓展 Session
這是底層機制,必須實現。