laravel router
router
app.php中初始化的时候实例化router类给app::router,参数为app对象
/**
* Bootstrap the router instance.
*
* @return void
*/
public function bootstrapRouter()
{
$this->router = new Router($this);
}
\Laravel\Lumen\Application 引入了\Laravel\Lumen\Concerns\RoutesRequests,在初始化完成后执行里面的\Laravel\Lumen\Concerns\RoutesRequests::run方法,大致分为解析请求,以及发送请求
public function run($request = null)
{
$response = $this->dispatch($request);
if ($response instanceof SymfonyResponse) {
$response->send();
} else {
echo (string) $response;
}
if (count($this->middleware) > 0) {
$this->callTerminableMiddleware($response);
}
}
public function dispatch($request = null)
{
list($method, $pathInfo) = $this->parseIncomingRequest($request);
try {
return $this->sendThroughPipeline($this->middleware, function () use ($method, $pathInfo) {
if (isset($this->router->getRoutes()[$method.$pathInfo])) {
return $this->handleFoundRoute([true, $this->router->getRoutes()[$method.$pathInfo]['action'], []]);
}
return $this->handleDispatcherResponse(
$this->createDispatcher()->dispatch($method, $pathInfo)
);
});
} catch (Exception $e) {
return $this->prepareResponse($this->sendExceptionToHandler($e));
} catch (Throwable $e) {
return $this->prepareResponse($this->sendExceptionToHandler($e));
}
}
解析构造request请求
protected function parseIncomingRequest($request)
{
if (! $request) {
$request = Request::capture();
}
$this->instance(Request::class, $this->prepareRequest($request));
return [$request->getMethod(), '/'.trim($request->getPathInfo(), '/')];
}
\Laravel\Lumen\Concerns\RoutesRequests::dispatch方法传入请求request,request不存在则创建一个(\Laravel\Lumen\Concerns\RoutesRequests::parseIncomingRequest)
public static function capture()
{
static::enableHttpMethodParameterOverride();
return static::createFromBase(SymfonyRequest::createFromGlobals());
}
public static function createFromBase(SymfonyRequest $request)
{
if ($request instanceof static) {
return $request;
}
$content = $request->content;
$request = (new static)->duplicate(
$request->query->all(), $request->request->all(), $request->attributes->all(),
$request->cookies->all(), $request->files->all(), $request->server->all()
);
$request->content = $content;
$request->request = $request->getInputSource();
return $request;
}
-
启用对http method的重写
-
\Illuminate\Http\Request::createFromBase 根据symfony组件创建一个基础请求
public static function createFromGlobals() { $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH']) ) { parse_str($request->getContent(), $data); $request->request = new ParameterBag($data); } return $request; }-
参数 SymfonyRequest::createFromGlobals()就是Symfony组件有关请求参数处理的返回,主要处理了php的超全局变量比如get,post数据等,构建一个request对象
-
\Symfony\Component\HttpFoundation\Request::createRequestFromFactory这里就是创建一个request,可以看到下面实力化当前类,new static()注意(new static 和new self的区别,new static的实例是由调用者决定的),调用initialize方法对对象进行赋值操作
private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { if (self::$requestFactory) { $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); if (!$request instanceof self) { throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); } return $request; } return new static($query, $request, $attributes, $cookies, $files, $server, $content); } -
\Symfony\Component\HttpFoundation\ServerBag::getHeaders 从server取出来数据,设置header头,
-
server里的http_开头的截取http_后边的都以key=>value形式存储起来,其余的['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true]覆盖
-
判断PHP 授权认证PHP_AUTH_USER是否存在存在,则PHP_AUTH_PW和PHP_AUTH_USER存起来,如果没有 PHP_AUTH_USER ,则在 server 中寻找关于 HTTP_AUTHORIZATION、REDIRECT_HTTP_AUTHORIZATION、AUTHORIZATION 的key value建值对,(这里针对apache,php-cgi不会把HTTP Basic Auth 传递给php,作者建议再.htaccess里添加)
\Symfony\Component\HttpFoundation\ServerBag::getHeaders
-
-
如果 请求的方法在['PUT', 'DELETE', 'PATCH']里面并且 from头不是application/x-www-form-urlencoded表单请求,则解析生成的request请求的内容给具体的变量,重新覆盖赋值给$request->request属性
-
-
从symfony实例创建的request对象克隆并重写一些方法,对里面的空的文件对象过滤掉\Illuminate\Http\Request::duplicate
-
-
request注册到容器中,这里会在request对象上设置用户auth回调以及当前路由的回调\Laravel\Lumen\Application::prepareRequest,这在下一步发送请求的时候由管道去执行。
发送请求
这里发送请求,\Laravel\Lumen\Concerns\RoutesRequests::sendThroughPipeline这里处理了中间件的情况,中间件是在app初始化的时候绑定上的(\Laravel\Lumen\Concerns\RoutesRequests::middleware) ,通过管道发送对象,这里设置了发送的对象,以及所用的中间件,然后在then方法里统一处理\Illuminate\Pipeline\Pipeline::then()
protected function sendThroughPipeline(array $middleware, Closure $then)
{
if (count($middleware) > 0 && ! $this->shouldSkipMiddleware()) {
return (new Pipeline($this))
->send($this->make('request'))
->through($middleware)
->then($then);
}
return $then();
}
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
list($name, $parameters) = $this->parsePipeString($pipe);
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
$parameters = [$passable, $stack];
}
$response = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $response instanceof Responsable
? $response->toResponse($this->container->make(Request::class))
: $response;
};
};
}
-
这里依赖于PHP的reduce函数,array_reduce函数处理把app初始化绑定的中间件一个一个的处理,这里会返回一个闭包,\Illuminate\Pipeline\Pipeline::carry 这是reduce的匿名函数,每次处理都会返回一个闭包,闭包里面处理了发送本次请求的回调,如果管道流pipe(中间件)是一个匿名函数则直接调用匿名函数,返回结果,如果不是一个对象则解析获取管道类名称以及参数,注入容器,如果是一个对象,合并参数,最后执行中间件handle函数,返回一个response对象。App\Http\Middleware\CORSMiddleware假设这是中间件,则method_exists判断handle在中间中,执行handle,hanlde中
request),相当于执行上面\Illuminate\Pipeline\Pipeline::prepareDestination这里返回的匿名函数了。
-
这里有个不太容易理解的地方,假设有两个中间件,App\Http\Middleware\CORSMiddleware1, App\Http\Middleware\CORSMiddleware2
- \Illuminate\Pipeline\Pipeline::prepareDestination函数返回的闭包是第一个
pipe为 App\Http\Middleware\CORSMiddleware3 中间件事一个字符串 carry第一次返回的暂记为callback1,
- 在第二次迭代的时候,callback1作为
pipe的值是 App\Http\Middleware\CORSMiddleware2
这里闭包是闭包套闭包,重点就在结束的时候统一执行,也能解释了为什么要在上面把数组的顺序反转下,就是因为在最终生成的闭包里保证执行顺序是一开始设定的。
class CORSMiddleware1 { public function handle($name, Closure $next) { echo "这是中间件1\n"; $next($name); } } class CORSMiddleware2 { public function handle($name, Closure $next) { echo "这是中间件2\n"; $next($name); } } $prepareDestination = function () { return function ($name) { echo "这是最后一块的洋葱,那么中间件都执行完了,是不是到我了-- {$name}"; }; }; function carry() { return function ($stack, $pipe) { return function ($name) use ($stack, $pipe) { return (new $pipe())->handle($name, $stack); }; }; } $res = array_reduce(array_reverse([CORSMiddleware1::class, CORSMiddleware2::class]), carry(), $prepareDestination()); var_dump($res()); - \Illuminate\Pipeline\Pipeline::prepareDestination函数返回的闭包是第一个
-
上一步中间件处理完成后调用next()最终会执行一开始设置的发送请求设置的回调,寻找路由就是\Laravel\Lumen\Concerns\RoutesRequests::sendThroughPipeline这个方法的第二个参数
- 判断路由是否存在,存在则解析找到路由,中间件如果存在,重复上一个步骤,由管道处理中间件,处理完中间件后调用路由的回调函数,准备发送响应\Laravel\Lumen\Concerns\RoutesRequests::callActionOnArrayBasedRoute
- 如果路由不存在,则创建一个路由实例,实例不存在则创建一个FastRoute实例,调用addRoute添加进去,重复上面分发路由











网友评论