【Laravel-海贼王系列】第十四章,Session 解析

栏目: PHP · 发布时间: 6年前

内容简介:实现机参考文末拓展。我们从路由调用控制器的代码来反推比较好理解!

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);
   }
复制代码

这里主要看 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 属性

【Laravel-海贼王系列】第十四章,Session 解析

我们从中解析出 web 字符串返回。

  • 接着看 return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);

    这段代码主要功能就是从 $this->middleware$this->middlewareGroups 中解析出 $name 对应的中间件。

    我们上面解析的 web 字符串就是传递到这里的 $name

    那么 $this->middleware$this->middlewareGroups 是什么?我们先看图在分析怎么来的!

【Laravel-海贼王系列】第十四章,Session 解析

这两个属性是在内核的构造函数注入的 App\Http\Kernel 继承了 Illuminate\Foundation\Http\Kernelindex.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 对象的对应成员中
      }
  }
复制代码

返回值如下图

【Laravel-海贼王系列】第十四章,Session 解析
  • 最后还有个排序 return $this->sortMiddleware($middleware);
protected function sortMiddleware(Collection $middlewares)
{
    return (new SortedMiddleware($this->middlewarePriority, $middlewares))->all();
}
复制代码

这就是按照上面解析的 $this-middlewarePriority 的优先级进行排序。

分析 StartSession

StartSession 预览

上一步可以看到在 web 请求下我们是会默认通过 StartSession 中间件的。

我们来分析 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)
    {
        $config = $this->manager->getSessionConfig();

        if ($this->configHitsLottery($config)) {
            $session->getHandler()->gc($this->getSessionLifetimeInSeconds());
        }
    }

    protected function configHitsLottery(array $config)
    {
        return random_int(1, $config['lottery'][1]) <= $config['lottery'][0];
    }

    protected function storeCurrentUrl(Request $request, $session)
    {
        if ($request->method() === 'GET' &&
            $request->route() &&
            ! $request->ajax() &&
            ! $request->prefetch()) {
            $session->setPreviousUrl($request->fullUrl());
        }
    }

    protected function addCookieToResponse(Response $response, Session $session)
    {
        if ($this->usingCookieSessions()) {
            $this->manager->driver()->save();
        }

        if ($this->sessionIsPersistent($config = $this->manager->getSessionConfig())) {
            $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
            ));
        }
    }

    protected function getSessionLifetimeInSeconds()
    {
        return ($this->manager->getSessionConfig()['lifetime'] ?? null) * 60;
    }
    
    protected function getCookieExpirationDate()
    {
        $config = $this->manager->getSessionConfig();

        return $config['expire_on_close'] ? 0 : Carbon::now()->addMinutes($config['lifetime']);
    }

    protected function sessionConfigured()
    {
        return ! is_null($this->manager->getSessionConfig()['driver'] ?? null);
    }

    protected function sessionIsPersistent(array $config = null)
    {
        $config = $config ?: $this->manager->getSessionConfig();

        return ! in_array($config['driver'], [null, 'array']);
    }

    protected function usingCookieSessions()
    {
        if ($this->sessionConfigured()) {
            return $this->manager->driver()->getHandler() instanceof CookieSessionHandler;
        }

        return false;
    }
}

复制代码

这是整个 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 这个对象,接着就是调用 setRequestOnHandlerstart() 方法

这里我们跳过 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 对象才是真正和我们打交道的对象!

用户层的体验 Illuminate\Session\Store

通过上面的分析,我么知道 Laravel 屏蔽了数据驱动层,直接向上层

提供了 Store 对象来实现对整个 Session 的调用,用户不需要在关心

底层的实现逻辑,只需要按照配置设定好驱动然后调用 Store 中提供的方法即可!

最后我们所有的 get() set() flush() 等等操作只不过是 Store 提供的服务。

拓展--实现 SessionHandlerInterface

关于实现 implements SessionHandlerInterface

其实 PHP 的针对自定义 Session 提供了预留接口,要自己拓展就必须实现这个接口中定义的方法,

PHP 底层会通过这几个方法将 Session ID 传递进来。

结语

通过本章我们要了解几个重点

  • StartSession 中间件的启动过程 ( Kernel 中配置)
  • Session 驱动的加载方式 (通过 SessionManager 工厂加载)
  • 用户最后针对 Session 的所有操作是由 Illuminate\Session\Store 对象提供
  • PHP 提供的 SessionHandlerInterface 来拓展自定义 Session 这是底层交互,必须实现。

以上所述就是小编给大家介绍的《【Laravel-海贼王系列】第十四章,Session 解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms for Image Processing and Computer Vision

Algorithms for Image Processing and Computer Vision

Parker, J. R. / 2010-12 / 687.00元

A cookbook of algorithms for common image processing applications Thanks to advances in computer hardware and software, algorithms have been developed that support sophisticated image processing with......一起来看看 《Algorithms for Image Processing and Computer Vision》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试