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
这是底层机制,必须实现。