zoukankan      html  css  js  c++  java
  • Laravel 源码解析(一)

    之前就想学着看源码了,无奈总是半途而废,这次希望能学完,让自己沉淀下。

    从入口文件index.php的第一行开始把,

    define('LARAVEL_START', microtime(true));
    
    require __DIR__.'/../vendor/autoload.php';

    第一行代码表示记录项目开始加载的时间,然后加载composer自动加载文件。

    $app = require_once __DIR__.'/../bootstrap/app.php';

    这里获取app变量,这里是整个项目的应用实例,后续还会有很多地方用到他,这里先跳到app.php文件去看看.

    app.php文件解析:

    $app = new IlluminateFoundationApplication(
        realpath(__DIR__.'/../')
    );

    这里把项目目录地址的绝对路径传入Application类中进行初始化,现在要跳往Application类去看下了:

    public function __construct($basePath = null)
        {
            if ($basePath) {
                $this->setBasePath($basePath);
            }
    
            $this->registerBaseBindings();
    
            $this->registerBaseServiceProviders();
    
            $this->registerCoreContainerAliases();
        }

    这个类继承自IlluminateContainerContainer,说明其实整个laravel是一个巨大的容器。

    如果传入了项目地址,则首先通过方法setBasePath方法设置基础路径,并在此方法中调用bindPathsInContainer方法初始化一系列目录地址:

    public function setBasePath($basePath)
        {
            $this->basePath = rtrim($basePath, '/');
    
            $this->bindPathsInContainer();
    
            return $this;
        }
    
        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());
        }

    registerBaseBindings方法如下:

    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()
            ));
        }

    static::setInstance($this) 这个段代码是把当前类也就是Application赋值给自身的一个静态变量$instance,应该是实现了单例模式吧。

    instance 方法绑定一个已存在的对象实例到容器,随后调用容器将总是返回给定的实例.

    重新回到构造函数里,$this->registerBaseServiceProviders()里注册基础的服务提供者,代码如下:

    $this->register(new EventServiceProvider($this));
    
    $this->register(new LogServiceProvider($this));
    
    $this->register(new RoutingServiceProvider($this));    

    注册了事件服务提供者、日志服务提供者、路由服务提供者,这里的服务提供者都是IlluminateFoundationSupportProvidersServiceProvider的子类,构造函数接受一个Application实例。

    这里需要去看看register的代码是怎样的,如下:

    public function register($provider, $options = [], $force = false)
        {
            if (($registered = $this->getProvider($provider)) && ! $force) {
                return $registered;
            }
    
            // If the given "provider" is a string, we will resolve it, passing in the
            // application instance automatically for the developer. This is simply
            // a more convenient way of specifying your service provider classes.
            if (is_string($provider)) {
                $provider = $this->resolveProvider($provider);
            }
    
            if (method_exists($provider, 'register')) {
                $provider->register();
            }
    
            $this->markAsRegistered($provider);
    
            // If the application has already booted, we will call this boot method on
            // the provider class so it has an opportunity to do its boot logic and
            // will be ready for any usage by this developer's application logic.
            if ($this->booted) {
                $this->bootProvider($provider);
            }
    
            return $provider;
        }

    已注册的服务提供者实例会放在serviceProviders这个数组里,所以开头的代码意思是 如果能从这个数组里获取到实例并且force为false  应该是非强制注册吧,那么就地返回实例,否则往下看,如果$provider变量是字符串,则调用resolveProvider解析服务提供者,并返回实例:

    if (is_string($provider)) {
       $provider = $this->resolveProvider($provider);
    }
    
    public function resolveProvider($provider)
        {
            return new $provider($this);
        }

    继续往下看:

    if (method_exists($provider, 'register')) {
                $provider->register();
            }

    如果传入的服务提供者存在register方法,则调用其register方法。

    接着继续往下走实例服务提供者:

    $this->markAsRegistered($provider);
    
    protected function markAsRegistered($provider)
        {
            $this->serviceProviders[] = $provider;
    
            $this->loadedProviders[get_class($provider)] = true;
        }

    这里把服务提供者的实例放入serviceProviders数组缓存,并用loadedProviders这个数组标记为已实例状态。

    继续往下看,还有最后一段:

    if ($this->booted) {
                $this->bootProvider($provider);
            }
    
    protected function bootProvider(ServiceProvider $provider)
        {
            if (method_exists($provider, 'boot')) {
                return $this->call([$provider, 'boot']);
            }
        }

    这里的booted标识项目是否启动,默认为false,如果是true(也就是启动的话),则如果服务提供者里存在boot方法就会调用。

    再次回到构造函数里,这里还调用了registerCoreContainerAliases方法,看名字就知道是干嘛的,注册核心容器别名。

    到这里Application的初始化工作就完成了,回到app.php文件吧。

    $app->singleton(
        IlluminateContractsHttpKernel::class,
        AppHttpKernel::class
    );
    
    $app->singleton(
        IlluminateContractsConsoleKernel::class,
        AppConsoleKernel::class
    );
    
    $app->singleton(
        IlluminateContractsDebugExceptionHandler::class,
        AppExceptionsHandler::class
    );

    这里注册http请求Kernel对象、命令行Kernel对象、错误处理。

    最后返回$app应用对象。

    回到入口文件,往下看:

    $kernel = $app->make(IlluminateContractsHttpKernel::class);
    
    $response = $kernel->handle(
        $request = IlluminateHttpRequest::capture()
    );
    
    $response->send();
    
    $kernel->terminate($request, $response);

    先简单说一下:

    第一行从容器解析kernel对象,初始化。

    第二行捕获请求。

    第三行发送请求。

    第四行Kernel终止。

    下面详细说:

    第一行实际上解析AppHttpKernel对象,其代码并没有construct构造函数,但是它集成自IlluminateFoundationHttpKernel,那么我们追踪到这个类的构造函数:

    public function __construct(Application $app, Router $router)
        {
            $this->app = $app;
            $this->router = $router;
    
            $router->middlewarePriority = $this->middlewarePriority;
    
            foreach ($this->middlewareGroups as $key => $middleware) {
                $router->middlewareGroup($key, $middleware);
            }
    
            foreach ($this->routeMiddleware as $key => $middleware) {
                $router->aliasMiddleware($key, $middleware);
            }
        }

    middlewarePriority这个属性表示中间件的加载顺序,如果不想默认的话可以在AppHttpKernel对象重写这个属性。

    下面的代码就是加载中间件组合中间件了。

    第二,捕获请求,这里比较核心,和路由有关

    会调用handle方法,此方法依旧不在AppHttpKernel对象里,则还是回到老地方IlluminateFoundationHttpKernel对象代码:

    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 EventsRequestHandled($request, $response)
            );
    
            return $response;
        }

    $request->enableHttpMethodParameterOverride();这里开启方法欺骗.比如在post表单模拟put、delete、patch请求。

    继续看sendRequestThroughRouter这个方法。

    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());
        }

    首先把$request对象实例绑定到app容器里。

    $this->bootstrap() 再进行项目初始化,初始化类如下:

      1.IlluminateFoundationBootstrapDetectEnvironment 环境配置($app['env'])
      2.IlluminateFoundationBootstrapLoadConfiguration 基本配置($app['config'])
      3.IlluminateFoundationBootstrapConfigureLogging 日志文件($app['log'])
      4.IlluminateFoundationBootstrapHandleExceptions 错误&异常处理
      5.IlluminateFoundationBootstrapRegisterFacades 清除已解析的Facade并重新启动,注册config文件中alias定义的所有Facade类到容器
      6.IlluminateFoundationBootstrapRegisterProviders 注册config中providers定义的所有Providers类到容器
      7.IlluminateFoundationBootstrapBootProviders 调用所有已注册Providers的boot方法

    app里的hasBeenBootstrapped属性标识是否初始化过,默认为false,未初始化的情况下才会运行bootstrap。

    运行以上类中的bootstrap方法。

    在进行路由调度之前进行一些操作

    return (new Pipeline($this->app))
                        ->send($request)
                        ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                        ->then($this->dispatchToRouter());

    最后调用dispatchToRouter开始把请求映射到路由。

    protected function dispatchToRouter()
        {
            return function ($request) {
                $this->app->instance('request', $request);
    
                return $this->router->dispatch($request);
            };
        }

    此函数把当前请求request对象绑定到容器中,然后由route对象来匹配路由。

    先看下IlluminateRoutingRouter类吧.

    public function dispatch(Request $request)
        {
            $this->currentRequest = $request
    
            return $this->dispatchToRoute($request);
        }
    
        public function dispatchToRoute(Request $request)
        {
            return $this->runRoute($request, $this->findRoute($request));
        }
    
        protected function findRoute($request)
        {
            $this->current = $route = $this->routes->match($request);
    
            $this->container->instance(Route::class, $route);
    
            return $route;
        }

    这里匹配路由的逻辑主要在IlluminateRoutingRouteCollection的match方法里:

    public function match(Request $request)
        {
            $routes = $this->get($request->getMethod());
    
            $route = $this->matchAgainstRoutes($routes, $request);
    
            if (! is_null($route)) {
                return $route->bind($request);
            }
    
            $others = $this->checkForAlternateVerbs($request);
    
            if (count($others) > 0) {
                return $this->getRouteForMethods($request, $others);
            }
    
            throw new NotFoundHttpException;
        }

    $routes = $this->get($request->getMethod());表示根据当前请求方式匹配路由。

  • 相关阅读:
    Linux常用命令3
    清空指定表的所有记录数据。
    向已经创建好的表添加和删除指定的列族或列。
    在终端打印出指定表的所有记录数据。
    列出HBASE所有表的相关信息,如表名、创建时间等。
    我在校园自动签到系统
    charles配置
    计算机网络第7版 PDF+ 计算机网络释疑与习题解答第7版 PDF 计算机网络 课后答案
    在HDFS中将文件从源路径移动到目的路径。
    删除HDFS中指定的文件。
  • 原文地址:https://www.cnblogs.com/tudou1223/p/10935842.html
Copyright © 2011-2022 走看看