1、XDEBUG调试php
这里咱们须要用到php的 xdebug 拓展,因此须要小伙伴们本身去装一下,由于我这里用的是docker,因此就简单介绍下在docker中使用xdebug的注意点。nginx
一、在phpstorm中的 Perferences >> Languages & Framework >> PHP >> debug >> DBGp Proxy 中的Host填写的是宿主机的IP地址。能够在命令行中使用ifconfig / ipconfig查看你的本地IP。laravel
二、在 Perferences >> Languages & Framework >> PHP >> Servers 中将你本地项目地址映射到docker容器中的项目地址。web
三、xdeug.ini的配置。须要特别注意的就是 xdebug.remote_host 填的也是你的宿主机ip。 xdebug.remote_connect_back 配置必定要关掉,不然 xdebug.remote_host 就会不起做用。docker
当xdebug启动后,效果是这样的json
2、Laravel生命周期bootstrap
一、启动容器数组
咱们知道Laravel全部的请求都会被nginx转发到项目下的 public/index.php 文件中。闭包
<?php /** * Laravel - A PHP Framework For Web Artisans * * @package Laravel * @author Taylor Otwell <taylor@laravel.com> */ define('LARAVEL_START', microtime(true)); /* |-------------------------------------------------------------------------- | Register The Auto Loader |-------------------------------------------------------------------------- | | Composer provides a convenient, automatically generated class loader for | our application. We just need to utilize it! We'll simply require it | into the script here so that we don't have to worry about manual | loading any of our classes later on. It feels great to relax. | */ require __DIR__.'/../vendor/autoload.php'; /* |-------------------------------------------------------------------------- | Turn On The Lights |-------------------------------------------------------------------------- | | We need to illuminate PHP development, so let us turn on the lights. | This bootstraps the framework and gets it ready for use, then it | will load up this application so that we can run it and send | the responses back to the browser and delight our users. | */ $app = require_once __DIR__.'/../bootstrap/app.php'; /* |-------------------------------------------------------------------------- | Run The Application |-------------------------------------------------------------------------- | | Once we have the application, we can handle the incoming request | through the kernel, and send the associated response back to | the client's browser allowing them to enjoy the creative | and wonderful application we have prepared for them. | */ $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); $response->send(); $kernel->terminate($request, $response);
很显然,第一件事就是 require __DIR__.'/../vendor/autoload.php' ,自动加载的加载。app
核心 vendor/composer/ClassLoader 类中的 findFIle 方法
public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; }
当自动加载注册完后,就开始启动Laravel了,下面 $app = require_once __DIR__.'/../bootstrap/app.php' 就是返回一个App实例,咱们看下这文件到底作了些啥。
<?php/* |-------------------------------------------------------------------------- | Create The Application |-------------------------------------------------------------------------- | | The first thing we will do is create a new Laravel application instance | which serves as the "glue" for all the components of Laravel, and is | the IoC container for the system binding all of the various parts. | */ $app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../') ); /* |-------------------------------------------------------------------------- | Bind Important Interfaces |-------------------------------------------------------------------------- | | Next, we need to bind some important interfaces into the container so | we will be able to resolve them when needed. The kernels serve the | incoming requests to this application from both the web and CLI. | */ $app->singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class ); $app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class ); $app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class ); /* |-------------------------------------------------------------------------- | Return The Application |-------------------------------------------------------------------------- | | This script returns the application instance. The instance is given to | the calling script so we can separate the building of the instances | from the actual running of the application and sending responses. | */ return $app;
首先new出一个app实例,但在其构造函数中,作了一些框架的初始化工做。
public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); }
在 setBasePath 中,向Laravel容器中注入了下面这些路径。
protected function bindPathsInContainer() { $this->instance('path', $this->path()); $this->instance('path.base', $this->basePath()); $this->instance('path.lang', $this->langPath()); $this->instance('path.config', $this->configPath()); $this->instance('path.public', $this->publicPath()); $this->instance('path.storage', $this->storagePath()); $this->instance('path.database', $this->databasePath()); $this->instance('path.resources', $this->resourcePath()); $this->instance('path.bootstrap', $this->bootstrapPath()); }
这也就是为何咱们在项目中可使用 storage_path() 等方法获取路径的缘由。
而后就是注册一些Laravel的基础绑定
protected function registerBaseBindings() { static::setInstance($this); $this->instance('app', $this); $this->instance(Container::class, $this); $this->instance(PackageManifest::class, new PackageManifest( new Filesystem, $this->basePath(), $this->getCachedPackagesPath() )); }
这里将app容器绑定到 app 和 Container::class 上,而后再绑定一个 PackageManifest::class 到容器中,这个类是在Laravel5.5后的新功能--包自动发现时用的。
而后注册了三个服务提供者,若是它们中有register方法,则执行其中的register方法。
protected function registerBaseServiceProviders() { $this->register(new EventServiceProvider($this)); $this->register(new LogServiceProvider($this)); $this->register(new RoutingServiceProvider($this)); }
至于它们的register方法都作了些啥,感兴趣的同窗能够去看下源码。最后 registerCoreContainerAliases() 方法就是给大多类注册了一些别名/简称。
以上这些动做咱们均可以经过xdebug来看到。让咱们看下app实例中如今都有那些动做已经完成了。
这是上面 registerBaseServiceProviders() 注册的三个服务提供者。
这个是上面注册的三个服务提供者里面register方法绑定到容器中的。
这是 registerBaseBindings 方法中绑定。
这两个属性是 registerCoreContainerAliases 方法中绑定的一些别名/简称。
而后连续三次调用 $app->singleton() ,分别将http,console和异常处理的实现类绑定到容器中。能够在app实例的bindings属性中看到
而后返回app实例,到此,这就是 bootstrap/app.php 文件作的全部事情。
咱们知道Laravel的核心就是服务容器,在 bootstrap/app.php 文件中,咱们向容器中绑定了不少服务,那么,咱们想要服务该怎么从容器中取呢?
回到 public/index.php 中,紧接着 $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); ,就是从容器中取出 Illuminate\Contracts\Http\Kernel::class 类,此时,咱们已经知道, Illuminate\Contracts\Http\Kernel::class 的实现类 App\Http\Kernel::class ,咱们也能够在xdebug中看到。
这里试想下,若是不用服务容器,本身new一个App\Http\Kernel类出来,会发现里面有不少依赖,即便你最后new出来了,相比服务容器,那也太麻烦了。
服务容器是使用依赖注入和 ReflectionClass 反射类实现的。
App\Http\Kernel父类的构造方法中就是将App\Http\Kernel类中$middlewareGroups和$routeMiddleware数组中的中间件绑定到Route::class中。
二、分发路由
后面调用的 $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); 像一个大黑盒子同样,在里面完成了全部的路由分发,中间件检测等工做。
先看一下 $request = Illuminate\Http\Request::capture() 方法;首先是经过php的超全局变量建立一个SymfonyRequest,而后再将SymfonyRequest转换Laravel可用的 Illuminate\Http\Request ,并丢入 handle() 方法中。
/** * Handle an incoming HTTP request. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function handle($request) { try { $request->enableHttpMethodParameterOverride(); $response = $this->sendRequestThroughRouter($request); } catch (Exception $e) { $this->reportException($e); $response = $this->renderException($request, $e); } catch (Throwable $e) { $this->reportException($e = new FatalThrowableError($e)); $response = $this->renderException($request, $e); } $this->app['events']->dispatch( new Events\RequestHandled($request, $response) ); return $response; }
里面的核心就是 sendRequestThroughRouter() 方法,经过字面意思也能够知道是--经过路由发送请求。
/** * Send the given request through the middleware / router. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ protected function sendRequestThroughRouter($request) { $this->app->instance('request', $request); Facade::clearResolvedInstance('request'); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }
$this->app->instance('request', $request); 和 Facade::clearResolvedInstance('request'); 分别表示绑定 Illuminate\Http\Request 到容器中和从Facade(目前并无)中删除request。
$this->bootstrap(); 是将 Illuminate\Foundation\Http\Kernel 中的 $bootstrappers 数组
protected $bootstrappers = [ \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ];
放到app容器中的 $this->app->bootstrapWith($this->bootstrappers()); 方法中一一执行。
public function bootstrapWith(array $bootstrappers) { $this->hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) { $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]); $this->make($bootstrapper)->bootstrap($this); $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]); } }
该方法首先触发 'bootstrapping: '.$bootstrapper 事件,代表 $bootstrapper 正在执行。当执行完 $bootstrapper 中的 bootstrap() 的方法后,触发 'bootstrapped: '.$bootstrapper 时事件,代表 $bootstrapper 执行完毕。下面咱们看下这个数组中到底作了什么。
一、 \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, 很显然,就是加载.env文件中的配置到项目中,它这里放在三个地方。
因此当咱们想要获取.env中的配置文件时,可使用getenv()、$_ENV或$_SERVER均可以。
二、 \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, 一样的,这是加载config文件下的全部配置文件。并将这些配置存在 Illuminate\Config\Repository 类的 $items 中。
三、 \Illuminate\Foundation\Bootstrap\HandleExceptions::class, 加载异常处理类
四、 \Illuminate\Foundation\Bootstrap\RegisterFacades::class, 注册 config/app.php 中的 aliases 数组和 bootstrap/cache/packages.php 的全部门面。
五、 \Illuminate\Foundation\Bootstrap\RegisterProviders::class, 注册 config/app.php 中的 providers 和 bootstrap/cache/packages.php 中的全部服务提供者,并将即便加载的和延迟加载的分开。记住,注册服务提供者时,若是该服务提供者有 register() 方法,则执行。
六、 \Illuminate\Foundation\Bootstrap\BootProviders::class, 若是存在的话,则执行上一步中注册的全部服务提供者中的 boot() 方法。
执行完 bootstrap() 方法。Laravel全部的加载工做都完成了,后面就开始经过 Pipeline 分发路由了。
return (new Pipeline($this->app)) ->send($request) // 设置须要分发的request ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) // 设置request须要经过的中间件 ->then($this->dispatchToRouter()); // 开始经过中间件
重点就是在这个then()方法中。
/** * Run the pipeline with a final destination callback. * * @param \Closure $destination * @return mixed */ public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination) ); return $pipeline($this->passable); }
形象点,能够用包洋葱来理解这里到底作了些什么?
array_reduce() 里就是将 App\Http\Kernel 中 $middleware 数组倒过来,经过 $this->carry() 将 $this->prepareDestination($destination) 像包洋葱同样一层层包起来。
能够经过xdebug看下最终包好的洋葱的样子。
洋葱包完了,咱们就该剥洋葱了。 return $pipeline($this->passable); 就是剥洋葱的整个动做。
具体怎么个剥法,能够在 carry() 方法中看到
protected function carry() { return function ($stack, $pipe) { return function ($passable) use ($stack, $pipe) { if (is_callable($pipe)) { // If the pipe is an instance of a Closure, we will just call it directly but // otherwise we'll resolve the pipes out of the container and call it with // the appropriate method and arguments, returning the results back out. return $pipe($passable, $stack); } elseif (! is_object($pipe)) {
// $pipe是中间件的类名因此会进入这里,parsePipeString方法是解析中间件有没有携带参数,若是没有则$parameters是一个空数组。 list($name, $parameters) = $this->parsePipeString($pipe); // If the pipe is a string we will parse the string and resolve the class out // of the dependency injection container. We can then build a callable and // execute the pipe function giving in the parameters that are required. $pipe = $this->getContainer()->make($name); // 从容器中解析出中间件类
// $passable就是request, $stack就是包洋葱时包进来的闭包 $parameters = array_merge([$passable, $stack], $parameters); } else { // If the pipe is already an object we'll just make a callable and pass it to // the pipe as-is. There is no need to do any extra parsing and formatting // since the object we're given was already a fully instantiated object. $parameters = [$passable, $stack]; }
// 若是中间件中有$this->method方法(其实就是handle方法),则传入$parameters参数,执行handle方法。 $response = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters); return $response instanceof Responsable ? $response->toResponse($this->container->make(Request::class)) : $response; }; };
一直到洋葱芯,其余外层都是中间件的处理,咱们找其中一个看一下,例如 \App\Http\Middleware\CheckForMaintenanceMode::class, 的handle方法。
/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed * * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ public function handle($request, Closure $next) {
// 判断项目是否下线 if ($this->app->isDownForMaintenance()) { $data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true); // 验证IP白名单 if (isset($data['allowed']) && IpUtils::checkIp($request->ip(), (array) $data['allowed'])) { return $next($request); } // 验证path是否在 $except 数组中 if ($this->inExceptArray($request)) { return $next($request); } throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']); } // 若是经过,则传request到闭包继续执行 return $next($request); }
全部的中间件处理方式都是同样的,具体哪一个中间件作了哪些操做,感兴趣的同窗能够本身去看下源码,这里不详细赘述。下面咱们直接看洋葱芯都作了些啥?
其实洋葱芯就是 dispatchToRouter() 方法中返回的一个闭包。
/** * Get the route dispatcher callback. * * @return \Closure */ protected function dispatchToRouter() { return function ($request) {
// 向容器中放入实例request $this->app->instance('request', $request);
// 经过\Illuminate\Routing\Router的dispatch方法分发request
return $this->router->dispatch($request); };
}
这个闭包里就是分发路由,而后再次像以前包洋葱和剥洋葱同样验证request中间件组里的中间件和路由配置里的中间件,最后执行控制器对应的方法。
至此Laravel整个生命周期就结束了。撒花🎉,下篇将简单介绍下Lumen框架的生命周期和解释下Lumen到底比Laravel轻在哪里?