搜索
首页php框架LaravelLaravel实例详解之容器、控制反转和依赖注入

本篇文章给大家带来了关于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删除
laravel单点登录方法详解laravel单点登录方法详解Jun 15, 2022 am 11:45 AM

本篇文章给大家带来了关于laravel的相关知识,其中主要介绍了关于单点登录的相关问题,单点登录是指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统,下面一起来看一下,希望对大家有帮助。

一起来聊聊Laravel的生命周期一起来聊聊Laravel的生命周期Apr 25, 2022 pm 12:04 PM

本篇文章给大家带来了关于laravel的相关知识,其中主要介绍了关于Laravel的生命周期相关问题,Laravel 的生命周期从public\index.php开始,从public\index.php结束,希望对大家有帮助。

laravel中guard是什么laravel中guard是什么Jun 02, 2022 pm 05:54 PM

在laravel中,guard是一个用于用户认证的插件;guard的作用就是处理认证判断每一个请求,从数据库中读取数据和用户输入的对比,调用是否登录过或者允许通过的,并且Guard能非常灵活的构建一套自己的认证体系。

laravel中asset()方法怎么用laravel中asset()方法怎么用Jun 02, 2022 pm 04:55 PM

laravel中asset()方法的用法:1、用于引入静态文件,语法为“src="{{asset(‘需要引入的文件路径’)}}"”;2、用于给当前请求的scheme前端资源生成一个url,语法为“$url = asset('前端资源')”。

实例详解laravel使用中间件记录用户请求日志实例详解laravel使用中间件记录用户请求日志Apr 26, 2022 am 11:53 AM

本篇文章给大家带来了关于laravel的相关知识,其中主要介绍了关于使用中间件记录用户请求日志的相关问题,包括了创建中间件、注册中间件、记录用户访问等等内容,下面一起来看一下,希望对大家有帮助。

laravel中间件基础详解laravel中间件基础详解May 18, 2022 am 11:46 AM

本篇文章给大家带来了关于laravel的相关知识,其中主要介绍了关于中间件的相关问题,包括了什么是中间件、自定义中间件等等,中间件为过滤进入应用的 HTTP 请求提供了一套便利的机制,下面一起来看一下,希望对大家有帮助。

laravel路由文件在哪个目录里laravel路由文件在哪个目录里Apr 28, 2022 pm 01:07 PM

laravel路由文件在“routes”目录里。Laravel中所有的路由文件定义在routes目录下,它里面的内容会自动被框架加载;该目录下默认有四个路由文件用于给不同的入口使用:web.php、api.php、console.php等。

laravel中的scope怎么用laravel中的scope怎么用Jun 09, 2022 am 11:15 AM

在laravel中,scope用于处理模型中的数据,在模型中可以定义scope开头方法,这类方法可以通过模型直接调用,被称为查询作用域,语法为“public function scope首字母大写单词($query){...}”。

See all articles

热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.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版