简介
Laravel
是完全废弃了 PHP
官方提供的 Session
服务而自己实现了。
实现机参考文末拓展。
开始,从路由的运行说起
我们从路由调用控制器的代码来反推比较好理解!
定位到【Laravel-海贼王系列】第十三章,路由&控制器解析的代码
// "这段就是路由调用控制器的地方"
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)
这个方法来获取中间件了
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);
}
复制代码
上面的代码我们分几步来拆解:
- 先看
$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()
返回值
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
属性
我们从中解析出 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
这是底层机制,必须实现。