目录
⒈ 容器
⒉ 服务提供者的注册以及服务的访问
⒊ 请求处理
⒋ 依赖注入
首页 php框架 Laravel Laravel实例详解之容器、控制反转和依赖注入

Laravel实例详解之容器、控制反转和依赖注入

Apr 07, 2022 pm 01:41 PM
laravel

本篇文章给大家带来了关于Laravel的相关知识,其中主要介绍了关于容器、控制反转以及依赖注入的相关问题,下面就一起来看一下什么相关的内容,希望对大家有帮助。

Laravel实例详解之容器、控制反转和依赖注入

推荐学习:Laravel入门

随着现在应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,耦合程度越来越高,经常会出现对象之间多重依赖的情况。对于如此庞大复杂的应用,任何修改都可能会牵一发而动全身,这就为应用的后期维护造成了很多困扰。

  为了解决对象之间耦合度高的问题,控制反转(IoC)的思想也随之诞生。所谓控制反转,是面向对象编程中的一种设计原则,其目的是为了降低代码之间的耦合程度。在 Laravel 中,控制反转是通过依赖注入(DI)的方式实现的。

  控制反转的基本思想是借助 IoC 容器实现对象之间的依赖关系的解耦。引入 IoC 容器之后,所有对象的控制权都上交给 IoC 容器,IoC 容器成了整个系统的核心,把所有对象粘合在一起发挥作用。Laravel 中的容器即起到了这个作用。

⒈ 容器

  所谓容器,在 Laravel 中指的是 \Illuminate\Foundation\Application 对象,Laravel 框架在启动时即创建了该对象。

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

  在创建容器的过程中,Laravel 还会对容器进行一些基础的绑定和服务注册。Laravel 首先会将容器实例与 app 和 Illuminate\Container\Container 进行绑定;之后,Laravel 会将基础的服务提供者注册到容器实例中,包括事件、日志、路由服务提供者;最后,Laravel 会将框架核心 class 与其相对应的别名一起注册到容器实例当中。

// 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;
}
登录后复制

  在完成这三步基本的注册之后,我们可以很方便的访问已经注册到容器中的对象实例。例如,可以直接通过 $app['app'] 或 $app['Illuminate\Container\Container'] 访问容器本身,还可以通过 $app['db'] 直接访问数据库连接。

⒉ 服务提供者的注册以及服务的访问

注册服务提供者

  在容器创建的过程中会注册基础服务提供者,其注册过程通过调用 register() 方法完成。

// 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 首先会判断指定的服务提供者是否已经在容器中注册(通过调用 getProvider() 方法实现),如果指定的服务提供者已经在容器中注册,并且本次注册操作并非强制执行,那么直接返回已经注册好的服务提供者。

  如果不满足上述条件,那么 Laravel 就会开始注册服务提供者。此时,如果传参为字符串,那么 Laravel 会默认参数为服务提供者的 class 名称并进行实例化(通过 resolveProvider() 方法实现)。之后,就会调用服务提供者定义的 register() 方法进行注册。以日志服务提供者为例,其 register() 方法的方法体如下

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

  register() 方法的作用就是将 Illuminate\Log\LogManager 对象以单例的模式注册到容器当中,注册完成之后,容器的 $bindings 属性中会增加一项

$app->bindings['log'] = [
    'concrete' => 'Illuminate\Log\LogManager {#162}',
    'shared' => true,
];
登录后复制

  如果服务提供者自身还定义了 $bindings 属性以及 $singletons 属性,那么 Laravel 还会调用相应的 bind() 方法和 singleton() 方法完成这些服务提供者自定义的绑定的注册。

  这之后 Laravel 会将服务提供者标记为已经注册的状态,随后会调用服务提供者定义的 boot() 方法启动服务提供者(前提是应用已经启动)。

  在向容器中注册绑定时,有 bind() 和 singleton() 两种方法,其区别仅在于注册的绑定是否为单例模式,即 shared 属性是否为 true 。

// 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
        );
    };
}
登录后复制

  仍然以日志服务提供者为例,日志服务提供者在注册时以单例模式进行注册,并且 $concrete 参数为闭包。在绑定开始之前,Laravel 首先会删除旧的绑定。由于此时 $concrete 为闭包,所以 Laravel 并不会进行什么操作,只是将绑定信息存入 $bindings 属性当中。

访问服务

  在服务提供者注册完成之后,我们可以用上文提到的类似访问数据库连接的方式那样访问服务。仍然以日志服务为例,我们可以通过 $app['log'] 的方式访问日志服务。另外,在 Laravel 中,我们还可以使用 facade 的方式访问服务,例如,我们可以调用 Illuminate\Support\Facades\Log::info() 来记录日志。

// 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入门

以上是Laravel实例详解之容器、控制反转和依赖注入的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Laravel和CodeIgniter的最新版本对比 Laravel和CodeIgniter的最新版本对比 Jun 05, 2024 pm 05:29 PM

Laravel9和CodeIgniter4的最新版本提供了更新的特性和改进。Laravel9采用MVC架构,提供数据库迁移、身份验证和模板引擎等功能。CodeIgniter4采用HMVC架构,提供路由、ORM和缓存。在性能方面,Laravel9的基于服务提供者设计模式和CodeIgniter4的轻量级框架使其具有出色的性能。在实际应用中,Laravel9适用于需要灵活性和强大功能的复杂项目,而CodeIgniter4适用于快速开发和小型应用程序。

Laravel 和 CodeIgniter 中数据处理能力的比较如何? Laravel 和 CodeIgniter 中数据处理能力的比较如何? Jun 01, 2024 pm 01:34 PM

比较Laravel和CodeIgniter的数据处理能力:ORM:Laravel使用EloquentORM,提供类对象关系映射,而CodeIgniter使用ActiveRecord,将数据库模型表示为PHP类的子类。查询构建器:Laravel具有灵活的链式查询API,而CodeIgniter的查询构建器更简单,基于数组。数据验证:Laravel提供了一个Validator类,支持自定义验证规则,而CodeIgniter的验证功能内置较少,需要手动编码自定义规则。实战案例:用户注册示例展示了Lar

Laravel 和 CodeIgniter 对于初学者来说哪一个更友好? Laravel 和 CodeIgniter 对于初学者来说哪一个更友好? Jun 05, 2024 pm 07:50 PM

对于初学者来说,CodeIgniter的学习曲线更平缓,功能较少,但涵盖了基本需求。Laravel提供了更广泛的功能集,但学习曲线稍陡。在性能方面,Laravel和CodeIgniter都表现出色。Laravel具有更广泛的文档和活跃的社区支持,而CodeIgniter更简单、轻量级,具有强大的安全功能。在建立博客应用程序的实战案例中,Laravel的EloquentORM简化了数据操作,而CodeIgniter需要更多的手动配置。

Laravel和CodeIgniter:哪种框架更适合大型项目? Laravel和CodeIgniter:哪种框架更适合大型项目? Jun 04, 2024 am 09:09 AM

在选择大型项目框架时,Laravel和CodeIgniter各有优势。Laravel针对企业级应用程序而设计,提供模块化设计、依赖项注入和强大的功能集。CodeIgniter是一款轻量级框架,更适合小型到中型项目,强调速度和易用性。对于具有复杂需求和大量用户的大型项目,Laravel的强大功能和可扩展性更合适。而对于简单项目或资源有限的情况下,CodeIgniter的轻量级和快速开发能力则更为理想。

Laravel - Artisan 命令 Laravel - Artisan 命令 Aug 27, 2024 am 10:51 AM

Laravel - Artisan 命令 - Laravel 5.7 提供了处理和测试新命令的新方法。它包括测试 artisan 命令的新功能,下面提到了演示?

Laravel和CodeIgniter:哪种框架更适合小型项目? Laravel和CodeIgniter:哪种框架更适合小型项目? Jun 04, 2024 pm 05:29 PM

对于小型项目,Laravel适用于大型项目,需要强大的功能和安全性。CodeIgniter适用于非常小的项目,需要轻量级和易用性。

Laravel 和 CodeIgniter 的模板引擎哪一个更好? Laravel 和 CodeIgniter 的模板引擎哪一个更好? Jun 03, 2024 am 11:30 AM

比较了Laravel的Blade和CodeIgniter的Twig模板引擎,根据项目需求和个人偏好进行选择:Blade基于MVC语法,鼓励良好代码组织和模板继承。Twig是第三方库,提供灵活语法、强大过滤器、扩展支持和安全沙箱。

Laravel - Artisan 控制台 Laravel - Artisan 控制台 Aug 27, 2024 am 10:51 AM

Laravel - Artisan Console - Laravel 框架提供了三种主要的命令行交互工具,即:Artisan、Ticker 和 REPL。本章详细介绍了 Artisan。

See all articles