Markdown

[Laravel] 從 public/index 開始的 Laravel Architecture Concepts

Laravel Architecture Concepts (DRAFT)

tags: Laravel

Index

  • Request Lifecycle

    • First Things
    • HTTP / Console Kernels
    • Service Providers
    • Dispatch Request
  • Service Container

    • Introduction
    • Binding
    • Resolving
    • Container Events
    • PSR-11
  • Service Providers

    • Introduction
    • Writing Service Providers
      • The Register Method
      • The Boot Method
    • Registering Providers
    • Deferred Providers

Request Lifecycle

First Things

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

bootstrap\app.php

  • Create The Application
  • Bind Important Interfaces
  • Return The Application
<?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( $_ENV['APP_BASE_PATH'] ?? dirname(__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;

Create The Application

new Illuminate\Foundation\Application()

透過觀察 app.php 可以發現一開始先 new 了一個 Application 實體 $app,
並且在 Application 中會將 Laravel 基本用到的工具、設定組合起來,
另外可以看見 Application 繼承了 Container,
Service Container (IoC Container) 可以把它視為進行 DI 的地方(類別),
Laravel 中的 Service Container 提供了方便的介面讓我們管理物件的依賴關係,
並且可以簡單的透過使用依賴注入(DI)達到符合控制反轉(IoC)的設計。

The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are “injected” into the class via the constructor or, in some cases, “setter” methods.

src\Illuminate\Foundation\Application.php

class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
    // ...
}

src\Illuminate\Container\Container.php

/** * Register a shared binding in the container. * * @param string $abstract * @param \Closure|string|null $concrete * @return void */ public function singleton($abstract, $concrete = null) { $this->bind($abstract, $concrete, true); }
/** * Register a binding with the container. * * @param string $abstract * @param \Closure|string|null $concrete * @param bool $shared * @return void */ public function bind($abstract, $concrete = null, $shared = false) { // unset 所有先前的綁定 $this->dropStaleInstances($abstract); // If no concrete type was given, we will simply set the concrete type to the // abstract type. After that, the concrete type to be registered as shared // without being forced to state their classes in both of the parameters. if (is_null($concrete)) { $concrete = $abstract; } // If the factory is not a Closure, it means it is just a class name which is // bound into this container to the abstract type and we will just wrap it // up inside its own Closure to give us more convenience when extending. if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact('concrete', 'shared'); // If the abstract type was already resolved in this container we'll fire the // rebound listener so that any objects which have already gotten resolved // can have their copy of the object updated via the listener callbacks. if ($this->resolved($abstract)) { $this->rebound($abstract); } }

var_dump 出 $this->bindings 的內容,可以看見啟動後第一個 binding 的就是 bootstrap\app.php 裡面的 Illuminate\Foundation\Application,並且 shared 為 true,表示註冊為 singleton

C:\Users\cepar\Desktop\projects\TDD\blog\vendor\laravel\framework\src\Illuminate\Container\Container.php:242:
array(1) {
  'Illuminate\Foundation\Mix' =>
  array(2) {
    'concrete' =>
    class Closure#4 (3) {
      public $static =>
      array(2) {
        ...
      }
      public $this =>
      class Illuminate\Foundation\Application#2 (33) {
        ...
      }
      public $parameter =>
      array(2) {
        ...
      }
    }
    'shared' =>
    bool(true)
  }
}

Bind Important Interfaces

$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
);

回頭看一開始的 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);

src\Illuminate\Foundation\Application.php

    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    public function make($abstract, array $parameters = [])
    {
        $this->loadDeferredProviderIfNeeded($abstract = $this->getAlias($abstract));

        return parent::make($abstract, $parameters);
    }

src\Illuminate\Container\Container.php

    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function make($abstract, array $parameters = [])
    {
        // Resolve the given type from the container.
        return $this->resolve($abstract, $parameters);
    }

Service providers

Service providers are the central place of all Laravel application bootstrapping. Your own application, as well as all of Laravel’s core services are bootstrapped via service providers.

public\index.php 中在 52 行透過 Service Container 取得 Http Kernel, 並接著處理到來的 Request,
接著看一下 Http Kernel 的 handle 做了哪些事。

src\Illuminate\Foundation\Http\Kernel.php

  • sendRequestThroughRouter
  • 送至 router 處理前先呼叫 bootstrap
  • bootstrap 依序把 bootstrappers 掛起來
  • bootstrappers 中有個 RegisterProviders,遍歷 config/app.php 裡的所有 providers,並將他們一一綁定
/** * 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 (Throwable $e) { $this->reportException($e); $response = $this->renderException($request, $e); } $this->app['events']->dispatch( new RequestHandled($request, $response) ); return $response; }
/** * 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()); }
/** * Bootstrap the application for HTTP requests. * * @return void */ public function bootstrap() { if (! $this->app->hasBeenBootstrapped()) { $this->app->bootstrapWith($this->bootstrappers()); } }
/** * Get the bootstrap classes for the application. * * @return array */ protected function bootstrappers() { return $this->bootstrappers; }
/** * The bootstrap classes for the application. * * @var array */ 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, ];

src\Illuminate\Foundation\Bootstrap\RegisterProviders.php

<?php namespace Illuminate\Foundation\Bootstrap; use Illuminate\Contracts\Foundation\Application; class RegisterProviders { /** * Bootstrap the given application. * * @param \Illuminate\Contracts\Foundation\Application $app * @return void */ public function bootstrap(Application $app) { $app->registerConfiguredProviders(); } }

src\Illuminate\Foundation\Application.php

/** * Register all of the configured providers. * * @return void */ public function registerConfiguredProviders() { $providers = Collection::make($this->config['app.providers']) ->partition(function ($provider) { return strpos($provider, 'Illuminate\\') === 0; }); $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]); (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) ->load($providers->collapse()->toArray()); }

src\Illuminate\Foundation\ProviderRepository.php

<?php namespace Illuminate\Foundation; use Exception; use Illuminate\Contracts\Foundation\Application as ApplicationContract; use Illuminate\Filesystem\Filesystem; class ProviderRepository { /** * The application implementation. * * @var \Illuminate\Contracts\Foundation\Application */ protected $app; /** * The filesystem instance. * * @var \Illuminate\Filesystem\Filesystem */ protected $files; /** * The path to the manifest file. * * @var string */ protected $manifestPath; /** * Create a new service repository instance. * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Filesystem\Filesystem $files * @param string $manifestPath * @return void */ public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath) { $this->app = $app; $this->files = $files; $this->manifestPath = $manifestPath; } /** * Register the application service providers. * * @param array $providers * @return void */ public function load(array $providers) { $manifest = $this->loadManifest(); // First we will load the service manifest, which contains information on all // service providers registered with the application and which services it // provides. This is used to know which services are "deferred" loaders. if ($this->shouldRecompile($manifest, $providers)) { $manifest = $this->compileManifest($providers); } // Next, we will register events to load the providers for each of the events // that it has requested. This allows the service provider to defer itself // while still getting automatically loaded when a certain event occurs. foreach ($manifest['when'] as $provider => $events) { $this->registerLoadEvents($provider, $events); } // We will go ahead and register all of the eagerly loaded providers with the // application so their services can be registered with the application as // a provided service. Then we will set the deferred service list on it. foreach ($manifest['eager'] as $provider) { $this->app->register($provider); } $this->app->addDeferredServices($manifest['deferred']); } ......











References:

控制反轉 (IoC) 與 依賴注入 (DI)

Laravel如何實現依賴性注入?

What is Dependency Injection? - Fabien Potencier

laravel 源码解析

Introduction Laravel Service Container

Phpstorm Tips:

Phpstorm Navigation Between Bookmarks

FunctionShortcutUse this shortcut to…
Go to Bookmark <number>Ctrl+NumberNavigate to a numbered bookmark with the corresponding number.
Toggle BookmarkF11Turn anonymous bookmark on or off.
Toggle Bookmark with MnemonicCtrl+F11Turn bookmark with mnemonic on or off.
Show BookmarksShift+F11Open Bookmarks dialog to manage existing bookmarks and navigate between them.
Remove all bookmarksShift+F11 Ctrl+A Del顯示所有書籤,全選,刪除

Phpstorm Query console

FunctionShortcutUse this shortcut to…
Go To Referenced DataCtrl+BSwitch to a record that the current record references. If more than one record is referenced, select the target record in the popup that appears. Read more about related data in Navigate between related data. The command is not available if there are no referenced records.

IDEA最全的快捷攻略

留言