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 Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

這里把項目目錄地址的絕對路徑傳入Application類中進行初始化,現在要跳往Application類去看下了:

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

        $this->registerBaseBindings();

        $this->registerBaseServiceProviders();

        $this->registerCoreContainerAliases();
    }

這個類繼承自Illuminate\Container\Container,說明其實整個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));    

注冊了事件服務提供者、日志服務提供者、路由服務提供者,這里的服務提供者都是Illuminate\Foundation\Support\Providers\ServiceProvider的子類,構造函數接受一個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(
    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
);

這里注冊http請求Kernel對象、命令行Kernel對象、錯誤處理。

最后返回$app應用對象。

回到入口文件,往下看:

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

先簡單說一下:

第一行從容器解析kernel對象,初始化。

第二行捕獲請求。

第三行發送請求。

第四行Kernel終止。

下面詳細說:

第一行實際上解析App\Http\Kernel對象,其代碼並沒有construct構造函數,但是它集成自Illuminate\Foundation\Http\Kernel,那么我們追蹤到這個類的構造函數:

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這個屬性表示中間件的加載順序,如果不想默認的話可以在App\Http\Kernel對象重寫這個屬性。

下面的代碼就是加載中間件組合中間件了。

第二,捕獲請求,這里比較核心,和路由有關

會調用handle方法,此方法依舊不在App\Http\Kernel對象里,則還是回到老地方Illuminate\Foundation\Http\Kernel對象代碼:

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

$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.Illuminate\Foundation\Bootstrap\DetectEnvironment 環境配置($app['env'])
  2.Illuminate\Foundation\Bootstrap\LoadConfiguration 基本配置($app['config'])
  3.Illuminate\Foundation\Bootstrap\ConfigureLogging 日志文件($app['log'])
  4.Illuminate\Foundation\Bootstrap\HandleExceptions 錯誤&異常處理
  5.Illuminate\Foundation\Bootstrap\RegisterFacades 清除已解析的Facade並重新啟動,注冊config文件中alias定義的所有Facade類到容器
  6.Illuminate\Foundation\Bootstrap\RegisterProviders 注冊config中providers定義的所有Providers類到容器
  7.Illuminate\Foundation\Bootstrap\BootProviders 調用所有已注冊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對象來匹配路由。

先看下Illuminate\Routing\Router類吧.

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

這里匹配路由的邏輯主要在Illuminate\Routing\RouteCollection的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());表示根據當前請求方式匹配路由。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM