Rumah  >  Artikel  >  rangka kerja php  >  Penjelasan terperinci tentang contoh Laravel: bekas, penyongsangan kawalan dan suntikan kebergantungan

Penjelasan terperinci tentang contoh Laravel: bekas, penyongsangan kawalan dan suntikan kebergantungan

WBOY
WBOYke hadapan
2022-04-07 13:41:242525semak imbas

Artikel ini membawakan anda pengetahuan yang berkaitan tentang Laravel, yang terutamanya memperkenalkan isu berkaitan tentang bekas, penyongsangan kawalan dan suntikan kebergantungan Mari kita lihat kandungan yang berkaitan, semoga ia membantu semua orang.

Penjelasan terperinci tentang contoh Laravel: bekas, penyongsangan kawalan dan suntikan kebergantungan

Pembelajaran yang disyorkan: Bermula dengan Laravel

Apabila skala aplikasi semasa menjadi lebih besar dan lebih besar, kebergantungan antara objek juga peningkatan Ia menjadi lebih dan lebih kompleks, tahap gandingan semakin tinggi dan lebih tinggi, dan selalunya terdapat pelbagai kebergantungan antara objek. Untuk aplikasi yang begitu besar dan kompleks, sebarang pengubahsuaian mungkin menjejaskan seluruh badan, yang menyebabkan banyak masalah untuk penyelenggaraan aplikasi kemudian.

  Bagi menyelesaikan masalah gandingan tinggi antara objek, idea Inversion of Control (IoC) turut lahir. Apa yang dipanggil penyongsangan kawalan ialah prinsip reka bentuk dalam pengaturcaraan berorientasikan objek Tujuannya adalah untuk mengurangkan tahap gandingan antara kod. Dalam Laravel, penyongsangan kawalan dilaksanakan melalui suntikan kebergantungan (DI).

  Idea asas penyongsangan kawalan ialah menggunakan bekas IoC untuk memisahkan kebergantungan antara objek. Selepas pengenalan bekas IoC, kawalan semua objek diserahkan kepada bekas IoC Bekas IoC menjadi teras keseluruhan sistem, melekatkan semua objek untuk berfungsi. Bekas dalam Laravel berfungsi untuk tujuan ini.

⒈ Bekas

  Bekas yang dipanggil dalam Laravel merujuk kepada objek IlluminateFoundationApplication, yang dicipta apabila rangka kerja Laravel bermula.

# public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';
# bootstrap/app.php
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

  Semasa proses mencipta bekas, Laravel juga akan melaksanakan beberapa penjilidan asas dan pendaftaran perkhidmatan untuk bekas itu. Laravel mula-mula akan mengikat contoh kontena ke aplikasi dan IlluminateContainerContainer kemudian, Laravel akan mendaftarkan penyedia perkhidmatan asas kepada contoh kontena, termasuk acara, log dan penyedia perkhidmatan penghalaan akhirnya, Laravel akan mengikat kelas teras rangka kerja yang berkaitan; alias didaftarkan bersama-sama dengan contoh kontena.

// namespace Illuminate\Foundation\Application
public function __construct($basePath = null)
{
    if ($basePath) {
        $this->setBasePath($basePath);
    }
    $this->registerBaseBindings();
    $this->registerBaseServiceProviders();
    $this->registerCoreContainerAliases();
}
    
protected function registerBaseBindings()
{
    static::setInstance($this);
    $this->instance('app', $this);
    $this->instance(Container::class, $this);
    /* ... ... */
}
protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));
    $this->register(new LogServiceProvider($this));
    $this->register(new RoutingServiceProvider($this));
}
public function registerCoreContainerAliases()
{
    foreach ([
        'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
        /* ... ...*/
        'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class],
        'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
        /* ... ... */
        'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
        'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
        /* ... ... */
    ] as $key => $aliases) {
        foreach ($aliases as $alias) {
            $this->alias($key, $alias);
        }
    }
}
// namespace Illuminate\Container\Container
public function alias($abstract, $alias)
{
    if ($alias === $abstract) {
        throw new LogicException("[{$abstract}] is aliased to itself.");
    }
    $this->aliases[$alias] = $abstract;
    $this->abstractAliases[$abstract][] = $alias;
}

  Selepas melengkapkan tiga langkah asas pendaftaran ini, kami boleh mengakses kejadian objek yang telah didaftarkan dalam bekas dengan mudah. Contohnya, bekas itu sendiri boleh diakses terus melalui $app['app'] atau $app['IlluminateContainerContainer'] , dan sambungan pangkalan data boleh diakses terus melalui $app['db'] .

⒉ Pendaftaran penyedia perkhidmatan dan akses kepada perkhidmatan

Pendaftaran penyedia perkhidmatan

  Pembekal perkhidmatan asas akan didaftarkan semasa proses penciptaan kontena , proses pendaftaran diselesaikan dengan memanggil kaedah daftar().

// namespace Illuminate\Foundation\Application
public function register($provider, $force = false)
{
    if (($registered = $this->getProvider($provider)) && ! $force) {
        return $registered;
    }
    if (is_string($provider)) {
        $provider = $this->resolveProvider($provider);
    }
    $provider->register();
    if (property_exists($provider, 'bindings')) {
        foreach ($provider->bindings as $key => $value) {
            $this->bind($key, $value);
        }
    }
    if (property_exists($provider, 'singletons')) {
        foreach ($provider->singletons as $key => $value) {
            $this->singleton($key, $value);
        }
    }
    $this->markAsRegistered($provider);
    if ($this->isBooted()) {
        $this->bootProvider($provider);
    }
    return $provider;
}

    Laravel akan terlebih dahulu menentukan sama ada pembekal perkhidmatan yang ditentukan telah didaftarkan dalam bekas (dilaksanakan dengan memanggil kaedah getProvider() Jika pembekal perkhidmatan yang dinyatakan telah didaftarkan dalam bekas, dan pendaftaran ini Jika operasi tidak wajib, maka pembekal perkhidmatan yang berdaftar akan dikembalikan terus.

  Jika syarat di atas tidak dipenuhi, Laravel akan mula mendaftarkan pembekal perkhidmatan. Pada masa ini, jika parameter diluluskan sebagai rentetan, Laravel akan lalai kepada nama kelas pembekal perkhidmatan dan membuat seketika (melalui kaedah resolveProvider()). Selepas itu, kaedah daftar() yang ditakrifkan oleh pembekal perkhidmatan dipanggil untuk mendaftar. Mengambil penyedia perkhidmatan log sebagai contoh, badan kaedah kaedah daftar()nya adalah seperti berikut Fungsi kaedah daftar() adalah untuk mendaftarkan objek IlluminateLogLogManager ke dalam bekas dalam mod tunggal item

// namespace Illuminate\Log\LogServiceProvider
public function register()
{
    $this->app->singleton('log', function ($app) {
        return new LogManager($app);
    });
}

akan ditambahkan pada sifat $bindings kontena Jika pembekal perkhidmatan itu sendiri juga mentakrifkan sifat $bindings dan sifat $singleton, Laravel juga akan memanggil kaedah bind() yang sepadan dan. Kaedah singleton() melengkapkan pendaftaran pengikatan yang ditentukan oleh pembekal perkhidmatan ini.

$app->bindings['log'] = [
    'concrete' => 'Illuminate\Log\LogManager {#162}',
    'shared' => true,
];
  Selepas ini, Laravel akan menandakan pembekal perkhidmatan sebagai berdaftar, dan kemudian memanggil kaedah boot() yang ditakrifkan oleh pembekal perkhidmatan untuk memulakan pembekal perkhidmatan (dengan syarat aplikasi telah dimulakan).

  Apabila mendaftarkan pengikatan dalam bekas, terdapat dua kaedah, bind() dan singleton() Satu-satunya perbezaan terletak pada sama ada pengikatan berdaftar berada dalam mod tunggal, iaitu sama ada atribut kongsi adalah benar .

 Masih mengambil penyedia perkhidmatan log sebagai contoh, pembekal perkhidmatan log didaftarkan dalam mod tunggal semasa mendaftar, dan parameter $concrete ialah penutupan. Sebelum pengikatan bermula, Laravel terlebih dahulu mengeluarkan ikatan lama. Memandangkan $concrete adalah penutupan pada masa ini, Laravel tidak akan melakukan sebarang operasi, tetapi hanya menyimpan maklumat yang mengikat dalam sifat $bindings.

// namespace Illuminate\Container\Container
public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}
public function bind($abstract, $concrete = null, $shared = false)
{
    // 删除旧的绑定
    $this->dropStaleInstances($abstract);
    if (is_null($concrete)) {
        $concrete = $abstract;
    }
    if (! $concrete instanceof Closure) {
        if (! is_string($concrete)) {
            throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
        }
        $concrete = $this->getClosure($abstract, $concrete);
    }
    $this->bindings[$abstract] = compact('concrete', 'shared');
    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
}
protected function getClosure($abstract, $concrete)
{
    return function ($container, $parameters = []) use ($abstract, $concrete) {
        if ($abstract == $concrete) {
            return $container->build($concrete);
        }
        return $container->resolve(
            $concrete, $parameters, $raiseEvents = false
        );
    };
}

Mengakses perkhidmatan

  Selepas pendaftaran pembekal perkhidmatan selesai, kami boleh mengakses perkhidmatan dengan cara yang sama seperti mengakses sambungan pangkalan data yang dinyatakan di atas. Masih mengambil perkhidmatan log sebagai contoh, kami boleh mengakses perkhidmatan log melalui $app['log']. Selain itu, dalam Laravel, kita juga boleh menggunakan fasad untuk mengakses perkhidmatan Sebagai contoh, kita boleh memanggil IlluminateSupportFacadesLog::info() untuk merekodkan log.

// namespace Illuminate\Support\Facades\Log
class Log extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'log';
    }
}
// namespace Illuminate\Support\Facades\Facade
public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();
    /* ... ... */
    return $instance->$method(...$args);
}
public static function getFacadeRoot()
{
    return static::resolveFacadeInstance(static::getFacadeAccessor());
}
protected static function resolveFacadeInstance($name)
{
    if (is_object($name)) {
        return $name;
    }
    if (isset(static::$resolvedInstance[$name])) {
        return static::$resolvedInstance[$name];
    }
    if (static::$app) {
        return static::$resolvedInstance[$name] = static::$app[$name];
    }
}

  在通过静态调用的方式进行日志记录时,首先会访问 Facade 中的魔术方法 __callStatic() ,该方法的首先进行的就是解析出 facade 对应的服务实例,然后调用该服务实例下的方法来执行相应的功能。每个 facade 中都会定义一个 getFacadeAccessor() 方法,这个方法会返回一个 tag,在日志服务中,这个 tag 就是日志服务提供者的闭包在容器的 $bindings 属性中的 key。也就是说,通过 facade 方式最终得到的是 $app['log']。

  那么为什么可以通过关联数组的方式访问容器中注册的对象/服务?Illuminate\Container\Container 实现了 ArrayAccess 并且定义了 OffsetGet() 方法,而 Illuminate\Foundation\Application 继承了 Container ,$app 为 Application 实例化的对象,所以通过关联数组的方式访问容器中注册的对象时会访问 Container 的 OffsetGet() 方法。在 OffsetGet() 方法中会调用 Container 的 make() 方法,而 make() 方法中又会调用 resolve() 方法。resolve() 方法最终会解析并返回相应的对象。

// namespace Illuminate\Container
public function offsetGet($key)
{
    return $this->make($key);
}
public function make($abstract, array $parameters = [])
{
    return $this->resolve($abstract, $parameters);
}
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
    /* ... ... */
    $this->with[] = $parameters;
    if (is_null($concrete)) {
        $concrete = $this->getConcrete($abstract);
    }
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete);
    } else {
        $object = $this->make($concrete);
    }
    /* ... ... */
    $this->resolved[$abstract] = true;
    array_pop($this->with);
    return $object;
}
protected function getConcrete($abstract)
{
    if (isset($this->bindings[$abstract])) {
        return $this->bindings[$abstract]['concrete'];
    }
    return $abstract;
}
protected function isBuildable($concrete, $abstract)
{
    return $concrete === $abstract || $concrete instanceof Closure;
}
public function build($concrete)
{
    if ($concrete instanceof Closure) {
        return $concrete($this, $this->getLastParameterOverride());
    }
    /* ... ... */
}
protected function getLastParameterOverride()
{
    return count($this->with) ? end($this->with) : [];
}

  这里需要说明,在通过 $app['log'] 的方式解析日志服务实例时,resolve() 方法中的 $concrete 解析得到的是一个闭包,导致 isBuildable() 方法返回结果为 true,所以 Laravel 会直接调用 build() 方法。而由于此时 $concrete 是一个闭包,所以在 build() 方法中会直接执行这个闭包函数,最终返回 LogManager 实例。

⒊ 请求处理

  在基础的绑定和服务注册完成之后,容器创建成功并返回 $app 。之后 Laravel 会将内核(包括 Http 内核和 Console 内核)和异常处理注册到容器当中。然后 Laravel 开始处理请求。

// namespace bootstrap/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
);
// public/index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Request::capture()
)->send();
$kernel->terminate($request, $response);

  在开始处理请求之前,Laravel 首先会解析出 Http 内核对象 $kernel,即 App\Http\Kernel 实例化的对象。而 App\Http\Kernel 继承了 Illuminate\Foundation\Kernel,所以 $kernel 实际调用的是 Illuminate\Foundation\Kernel 中的 handle() 方法。

namespace Illuminate\Foundation\Http
use Illuminate\Contracts\Debug\ExceptionHandler
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;
}
// 上报错误
protected function reportException(Throwable $e)
{
    $this->app[ExceptionHandler::class]->report($e);
}
// 渲染错误信息    
protected function renderException($request, Throwable $e)
{
    return $this->app[ExceptionHandler::class]->render($request, $e);
}

  handle() 方法在处理请求的过程中如果出现任何异常或错误,Laravel 都会调用容器中已经注册好的异常处理对象来上报异常并且渲染返回信息。

  在容器创建成功以后,Laravel 会将 Illuminate\Contracts\Debug\ExceptionHandler 和 App\Exceptions\Handler 之间的绑定注册到容器当中,所以 Laravel 处理异常实际调用的都是 App\Exceptions\Handler 中的方法。在实际开发过程中,开发者可以根据自身需要在 App\Exceptions\Handler 中自定义 report() 和 render() 方法。

在 PHP 7 中,`Exception` 和 `Error` 是两种不同的类型,但它们同时都继承了 `Throwable` ,所以 `handler()` 方法中捕获的是 `Throwable` 对象。

  在正式开始处理请求之前,Laravel 会进行一些引导启动,包括加载环境变量、配置信息等,这些引导启动在 Laravel 运行过程中起到了非常重要的作用。

// namespace Illuminate\Foundation\Http\Kernel
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,
];
protected function sendRequestThroughRouter($request)
{
    /* ... ... */
    $this->bootstrap();
    /* ... ... */
}
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}
// namespace Illuminate\Foundation\Application
public function bootstrapWith(array $bootstrappers)
{
    $this->hasBeenBootstrapped = true;
    foreach ($bootstrappers as $bootstrapper) {
        $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
        $this->make($bootstrapper)->bootstrap($this);
        $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
    }
}

  从代码中可以看出,引导启动的过程实际就是调用各个 class 中的 bootstrap() 方法。其中:

LoadEnvironmentVariables 用来加载环境变量

LoadConfiguration 用来加载 config 目录下的配置文件

HandleExceptions 用来设置 PHP 的错误报告级别以及相应的异常和错误处理函数,另外还会设置 PHP 的程序终止执行函数

// namespace Illuminate\Foundation\Bootstrap\HandleExceptions
public function bootstrap(Application $app)
{
    /* ... ... */
    $this->app = $app;
    error_reporting(-1);
    set_error_handler([$this, 'handleError']);
    set_exception_handler([$this, 'handleException']);
    register_shutdown_function([$this, 'handleShutdown']);
    /* ... ... */
}
public function handleError($level, $message, $file = '', $line = 0, $context = [])
{
    if (error_reporting() & $level) {
        /* ... ... */
        throw new ErrorException($message, 0, $level, $file, $line);
    }
}
public function handleException(Throwable $e)
{
    /* ... ... */
        $this->getExceptionHandler()->report($e);
    /* ... ... */
}
public function handleShutdown()
{
    if (! is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
        $this->handleException($this->fatalErrorFromPhpError($error, 0));
    }
}
protected function getExceptionHandler()
{
    return $this->app->make(\Illuminate\Contracts\Debug\ExceptionHandler::class);
}

  从以上代码中可以看出,虽然 HandleExceptions 中定义了异常、错误、程序终止的处理函数,但无论是哪种情况,最终还是调用 App\Exceptions\Handler 中的方法来处理异常或错误。

RegisterFacades 的作用一个是注册配置文件以及第三方包中自定义的 alias 类,还有一个非常重要的作用就是为 Illuminate\Support\Facades\Facade 类设置 $app 属性。

// namespace Illuminate\Foundation\Bootstrap\RegisterFAcades
public function bootstrap(Application $app)
{
    Facade::clearResolvedInstances();
    Facade::setFacadeApplication($app);
    AliasLoader::getInstance(array_merge(
        $app->make('config')->get('app.aliases', []),
        $app->make(PackageManifest::class)->aliases()
    ))->register();
}

&emsp 我们在通过 facade 方式反问容器中注册的服务时,Facade 在解析容器中的服务实例时用到的 static::$app 即是在这个时候设置的。

RegisterProviders 的作用是注册配置文件以及第三方包中定义的服务提供者

// namespace Illuminate\Foundation\Bootstrap\RegisterProviders
public function bootstrap(Application $app)
{
    $app->registerConfiguredProviders();
}
public function registerConfiguredProviders()
{
    $providers = Collection::make($this->make('config')->get('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());
}

  在实际注册的过程中,Laravel 会按照 Laravel 框架的服务提供者 > 第三方包的服务提供者 > 开发者自定义的服务提供者 的顺序进行注册

BootProviders 则是按顺序调用已经注册到容器中的服务提供者的 boot() 方法(前提是服务提供者定义的 boot() 方法)

  在引导启动完成之后,Laravel 开始处理请求,首先要做的就是将全局的中间件应用于 request 。这之后 Laravel 会将请求分发到相应的路由进行处理,处理之前需要先根据 request 找到相应的路由对象 Illuminate\Routing\Route。在 Laravel 中,除了全局中间件,还有一些中间件只作用于特定的路由或路由分组,此时这些中间件就会被作用于 request 。这些工作都完成之后,路由对象开始执行代码,完成请求。

// namespace Illuminate\Foundation\Http\Kernel
protected function sendRequestThroughRouter($request)
{
    /* ... ... */
    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);
        
        return $this->router->dispatch($request);
    };
}
// namespace 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 runRoute(Request $request, Route $route)
{
    /* ... ... */
    return $this->prepareResponse($request,
        $this->runRouteWithinStack($route, $request)
    );
}
protected function runRouteWithinStack(Route $route, Request $request)
{
    /* ... ... */   
    return (new Pipeline($this->container))
                    ->send($request)
                    ->through($middleware)
                    ->then(function ($request) use ($route) {
                        return $this->prepareResponse(
                            $request, $route->run()
                        );
                    });
}

⒋ 依赖注入

  Laravel 中的路由在注册时,action 可以是控制器方法,也可以是闭包。但无论是那种形式,都需要传参,而传参就会遇到需要依赖注入的情况。

  Route 对象在执行 run() 方法时会根据 action 的类型分别进行控制器方法调用或闭包函数的调用。但两种方法最终都需要解析参数,而如果参数中用到了 class ,就需要进行依赖注入。

// namespace Illuminate\Routing\Router
public function run()
{
    $this->container = $this->container ?: new Container;
    try {
        if ($this->isControllerAction()) {
            return $this->runController();
        }
        return $this->runCallable();
    } catch (HttpResponseException $e) {
        return $e->getResponse();
    }
}
protected function runController()
{
    return $this->controllerDispatcher()->dispatch(
        $this, $this->getController(), $this->getControllerMethod()
    );
}
protected function runCallable()
{
    /* ... ... */
    return $callable(...array_values($this->resolveMethodDependencies(
        $this->parametersWithoutNulls(), new ReflectionFunction($callable)
    )));
}
// namespace Illuminate\Routing\ControllerDispatcher
public function dispatch(Route $route, $controller, $method)
{
    $parameters = $this->resolveClassMethodDependencies(
        $route->parametersWithoutNulls(), $controller, $method
    );
    /* ... ... */
}
// namespace Illuminate\Routing\RouteDependencyResolverTrait
protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
{
    /* ... ... */
    return $this->resolveMethodDependencies(
        $parameters, new ReflectionMethod($instance, $method)
    );
}
public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)
{
    /* ... ... */
    foreach ($reflector->getParameters() as $key => $parameter) {
        $instance = $this->transformDependency($parameter, $parameters, $skippableValue);
        /* ... ... */
    }
    return $parameters;
}
protected function transformDependency(ReflectionParameter $parameter, $parameters, $skippableValue)
{
    $className = Reflector::getParameterClassName($parameter);
    if ($className && ! $this->alreadyInParameters($className, $parameters)) {
        return $parameter->isDefaultValueAvailable() ? null : $this->container->make($className);
    }
    return $skippableValue;
}

  在执行过程中,Laravel 首先通过反射取得参数列表(对于控制器方法,使用 ReflectionMethod ,对于闭包函数,则使用 ReflectionFunction )。在得到参数列表后,Laravel 仍然是利用反射,逐个判断参数类型。如果参数类型为 PHP 的内置类型,那么不需要什么特殊处理;但如果参数不是 PHP 内置类型,则需要利用反射解析出参数的具体类型。在解析出参数的具体类型之后,紧接着会判断该类型的对象是不是已经存在于参数列表中,如果不存在并且该类型也没有设置默认值,那么就需要通过容器创建出该类型的实例。

  要通过容器创建指定 class 的实例,仍然需要用到 resolve() 方法。前文已经叙述过使用 resolve() 方法解析闭包函数的情况,所以这里值叙述实例化 class 的情况。

// namespace Illuminate\Container\Container
public function build($concrete)
{
    /* ... ... */
    try {
        $reflector = new ReflectionClass($concrete);
    } catch (ReflectionException $e) {
        throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
    }
    if (! $reflector->isInstantiable()) {
        return $this->notInstantiable($concrete);
    }
    $this->buildStack[] = $concrete;
    $constructor = $reflector->getConstructor();
    if (is_null($constructor)) {
        array_pop($this->buildStack);
        return new $concrete;
    }
    $dependencies = $constructor->getParameters();
    try {
        $instances = $this->resolveDependencies($dependencies);
    } catch (BindingResolutionException $e) {
        array_pop($this->buildStack);
        throw $e;
    }
    array_pop($this->buildStack);
    return $reflector->newInstanceArgs($instances);
}
protected function resolveDependencies(array $dependencies)
{
    $results = [];
    foreach ($dependencies as $dependency) {
        if ($this->hasParameterOverride($dependency)) {
            $results[] = $this->getParameterOverride($dependency);
            continue;
        }
        $result = is_null(Util::getParameterClassName($dependency))
                        ? $this->resolvePrimitive($dependency)
                        : $this->resolveClass($dependency);
        if ($dependency->isVariadic()) {
            $results = array_merge($results, $result);
        } else {
            $results[] = $result;
        }
    }
    return $results;
}

  容器在实例化 class 的时候,仍然是通过反射获取 class 基本信息。对于一些无法进行实例化的 class (例如 interface 、abstract class ),Laravel 会抛出异常;否则 Laravel 会继续获取 class 的构造函数的信息。对于不存在构造函数的 class ,意味着这些 class 在实例化的时候不需要额外的依赖,可以直接通过 new 来实例化;否则仍然是通过反射解析出构造函数的参数列表信息,然后逐个实例化这些参数列表中用到的 class 。在这些参数列表中的 class 都实例化完成之后,通过容器创建 class 的准备工作也已经完成,此时容器可以顺利创建出指定 class 的实例,然后注入到控制器方法或闭包中。

推荐学习:Laravel入门

Atas ialah kandungan terperinci Penjelasan terperinci tentang contoh Laravel: bekas, penyongsangan kawalan dan suntikan kebergantungan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.im. Jika ada pelanggaran, sila hubungi admin@php.cn Padam