搜尋
首頁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如此受歡迎?Apr 02, 2025 pm 02:16 PM

Laravel受歡迎的原因包括其簡化開發過程、提供愉快的開發環境和豐富的功能。 1)它吸收了RubyonRails的設計理念,結合PHP的靈活性。 2)提供瞭如EloquentORM、Blade模板引擎等工具,提高開發效率。 3)其MVC架構和依賴注入機制使代碼更加模塊化和可測試。 4)提供了強大的調試工具和性能優化方法,如緩存系統和最佳實踐。

django或laravel哪個更好?django或laravel哪個更好?Mar 28, 2025 am 10:41 AM

Django和Laravel都是全棧框架,Django適合Python開發者和復雜業務邏輯,Laravel適合PHP開發者和優雅語法。 1.Django基於Python,遵循“電池齊全”哲學,適合快速開發和高並發。 2.Laravel基於PHP,強調開發者體驗,適合小型到中型項目。

哪個是更好的PHP或Laravel?哪個是更好的PHP或Laravel?Mar 27, 2025 pm 05:31 PM

PHP和Laravel不是直接可比的,因為Laravel是基於PHP的框架。 1.PHP適合小型項目或快速原型開發,因其簡單直接。 2.Laravel適合大型項目或高效開發,因其提供豐富功能和工具,但學習曲線較陡,性能可能不如純PHP。

Laravel是前端還是後端?Laravel是前端還是後端?Mar 27, 2025 pm 05:31 PM

laravelisabackendframeworkbuiltonphp,設計ForweBapplicationDevelopment.itfocusessonserver-sideLogic,databasemagemention和Applicationstructure和CanBeintegratedWithFrontendTechnologiesLikeLikeVue.jsorreActeReacterVue.jsorreActforforfull-stackDevefloct。

如何在Laravel中創建和使用自定義刀片指令?如何在Laravel中創建和使用自定義刀片指令?Mar 17, 2025 pm 02:50 PM

本文討論了Laravel中的創建和使用自定義刀片指令以增強模板。它涵蓋了定義指令,在模板中使用它們,並在大型項目中管理它們,強調了改進的代碼可重複性和R等好處

如何使用Laravel的組件來創建可重複使用的UI元素?如何使用Laravel的組件來創建可重複使用的UI元素?Mar 17, 2025 pm 02:47 PM

本文討論了使用組件在Laravel中創建和自定義可重複使用的UI元素,從而為組織提供最佳實踐並建議增強包裝。

如何使用Laravel的路由功能來創建SEO友好的URL?如何使用Laravel的路由功能來創建SEO友好的URL?Mar 17, 2025 pm 02:43 PM

文章討論了使用Laravel的路由來創建SEO友好的URL,涵蓋最佳實踐,規範的URL和SEO優化工具。WordCount:159

如何使用Laravel的工匠控制台自動執行常見任務?如何使用Laravel的工匠控制台自動執行常見任務?Mar 17, 2025 pm 02:39 PM

Laravel的工匠控制台可以自動化任務,例如生成代碼,運行遷移和調度。關鍵命令包括:​​控制器,遷移和DB:種子。可以為特定需求創建自定義命令,增強工作流效率。

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尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中