搜索
首页php框架Laravellaravel服务容器是什么

laravel服务容器是什么

Feb 14, 2022 pm 04:31 PM
laravel服务容器

在laravel中,服务容器是一个用于管理类依赖以及实现依赖注入的强有力工具。当应用程序需要使用某一个服务时,服务容器会将服务解析出来,并同时自动解决服务之间的依赖,然后交给应用程序使用。

laravel服务容器是什么

本教程操作环境:windows7系统、Laravel6版、Dell G3电脑。

什么是服务容器

Laravel 服务容器是一个用于管理类依赖以及实现依赖注入的强有力工具。依赖注入这个名词表面看起来花哨,实质上是指:通过构造函数,或者某些情况下通过「setter」方法将类依赖「注入」到类中。

Laravel中的功能模块比如 Route、Eloquent ORM、Request、Response等等等等,实际上都是与核心无关的类模块提供的,这些类从注册到实例化,最终被我们所使用,其实都是 laravel 的服务容器负责的。

服务容器中有两个概念控制反转(IOC)依赖注入(DI):

依赖注入和控制反转是对同一件事情的不同描述,它们描述的角度不同。依赖注入是从应用程序的角度在描述,应用程序依赖容器创建并注入它所需要的外部资源。而控制反转是从容器的角度在描述,容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

在Laravel中框架把自带的各种服务绑定到服务容器,我们也可以绑定自定义服务到容器。当应用程序需要使用某一个服务时,服务容器会将服务解析出来,同时自动解决服务之间的依赖,然后交给应用程序使用。

下面探讨一下Laravel中的服务绑定和解析是如何实现的。

服务绑定

常用的绑定服务到容器的方法有instance, bind, singleton, alias。下面我们分别来看一下。

instance

将一个已存在的对象绑定到服务容器里,随后通过名称解析该服务时,容器将总返回这个绑定的实例。

$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);

会把对象注册到服务容器的$instnces属性里

[
     'HelpSpot\Api' => $api//$api是API类的对象,这里简写了
 ]

bind

绑定服务到服务容器

有三种绑定方式:

1.绑定自身

$this->app->bind('HelpSpot\API', null);

2.绑定闭包

$this->app->bind('HelpSpot\API', function () {
    return new HelpSpot\API();
});//闭包直接提供类实现方式
$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});//闭包返回需要依赖注入的类

3. 绑定接口和实现

$this->app->bind('Illuminate\Tests\Container\IContainerContractStub', 'Illuminate\Tests\Container\ContainerImplementationStub');

针对第一种情况,其实在bind方法内部会在绑定服务之前通过getClosure()为服务生成闭包,我们来看一下bind方法源码。

public function bind($abstract, $concrete = null, $shared = false)
{
    $abstract = $this->normalize($abstract);
    
    $concrete = $this->normalize($concrete);
    //如果$abstract为数组类似['Illuminate/ServiceName' => 'service_alias']
    //抽取别名"service_alias"并且注册到$aliases[]中
    //注意:数组绑定别名的方式在5.4中被移除,别名绑定请使用下面的alias方法
    if (is_array($abstract)) {
        list($abstract, $alias) = $this->extractAlias($abstract);

        $this->alias($abstract, $alias);
    }

    $this->dropStaleInstances($abstract);

    if (is_null($concrete)) {
        $concrete = $abstract;
    }
    //如果只提供$abstract,则在这里为其生成concrete闭包
    if (! $concrete instanceof Closure) {
        $concrete = $this->getClosure($abstract, $concrete);
    }

    $this->bindings[$abstract] = compact('concrete', 'shared');

    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
}


protected function getClosure($abstract, $concrete)
{
    // $c 就是$container,即服务容器,会在回调时传递给这个变量
    return function ($c, $parameters = []) use ($abstract, $concrete) {
        $method = ($abstract == $concrete) ? 'build' : 'make';

        return $c->$method($concrete, $parameters);
    };
}

bind把服务注册到服务容器的$bindings属性里类似这样:

$bindings = [
    'HelpSpot\API' =>  [//闭包绑定
        'concrete' => function ($app, $paramters = []) {
            return $app->build('HelpSpot\API');
        },
        'shared' => false//如果是singleton绑定,这个值为true
    ]        
    'Illuminate\Tests\Container\IContainerContractStub' => [//接口实现绑定
        'concrete' => 'Illuminate\Tests\Container\ContainerImplementationStub',
        'shared' => false
    ]
]

singleton

public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}

singleton 方法是bind方法的变种,绑定一个只需要解析一次的类或接口到容器,然后接下来对于容器的调用该服务将会返回同一个实例

alias

把服务和服务别名注册到容器:

public function alias($abstract, $alias)
{
    $this->aliases[$alias] = $this->normalize($abstract);
}

alias 方法在上面讲bind方法里有用到过,它会把把服务别名和服务类的对应关系注册到服务容器的$aliases属性里。

例如:

$this->app->alias('\Illuminate\ServiceName', 'service_alias');

绑定完服务后在使用时就可以通过

$this->app->make('service_alias');

将服务对象解析出来,这样make的时候就不用写那些比较长的类名称了,对make方法的使用体验上有很大提升。

服务解析

make: 从服务容器中解析出服务对象,该方法接收你想要解析的类名或接口名作为参数

/**
 * Resolve the given type from the container.
 *
 * @param  string  $abstract
 * @param  array   $parameters
 * @return mixed
 */
public function make($abstract, array $parameters = [])
{
    //getAlias方法会假定$abstract是绑定的别名,从$aliases找到映射的真实类型名
    //如果没有映射则$abstract即为真实类型名,将$abstract原样返回
    $abstract = $this->getAlias($this->normalize($abstract));
    // 如果服务是通过instance()方式绑定的,就直接解析返回绑定的service
    if (isset($this->instances[$abstract])) {
        return $this->instances[$abstract];
    }
    // 获取$abstract接口对应的$concrete(接口的实现)
    $concrete = $this->getConcrete($abstract);
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete, $parameters);
    } else {
        //如果时接口实现这种绑定方式,通过接口拿到实现后需要再make一次才能
        //满足isBuildable的条件 ($abstract === $concrete)
        $object = $this->make($concrete, $parameters);
    }
    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }
    //如果服务是以singleton方式注册进来的则,把构建好的服务对象放到$instances里,
    //避免下次使用时重新构建
    if ($this->isShared($abstract)) {
        $this->instances[$abstract] = $object;
    }
    $this->fireResolvingCallbacks($abstract, $object);
    $this->resolved[$abstract] = true;
    return $object;
}
protected function getConcrete($abstract)
{
    if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
        return $concrete;
    }
    // 如果是$abstract之前没有注册类实现到服务容器里,则服务容器会认为$abstract本身就是接口的类实现
    if (! isset($this->bindings[$abstract])) {
        return $abstract;
    }
    return $this->bindings[$abstract]['concrete'];
}
protected function isBuildable($concrete, $abstract)
{        
    return $concrete === $abstract || $concrete instanceof Closure;
}

通过对make方法的梳理我们发现,build方法的职能是构建解析出来的服务的对象的,下面看一下构建对象的具体流程。(构建过程中用到了PHP类的反射来实现服务的依赖注入)

public function build($concrete, array $parameters = [])
{
    // 如果是闭包直接执行闭包并返回(对应闭包绑定)
    if ($concrete instanceof Closure) {
        return $concrete($this, $parameters);
    }
    
    // 使用反射ReflectionClass来对实现类进行反向工程
    $reflector = new ReflectionClass($concrete);
    // 如果不能实例化,这应该是接口或抽象类,再或者就是构造函数是private的
    if (! $reflector->isInstantiable()) {
        if (! empty($this->buildStack)) {
            $previous = implode(', ', $this->buildStack);
            $message = "Target [$concrete] is not instantiable while building [$previous].";
        } else {
            $message = "Target [$concrete] is not instantiable.";
        }
        throw new BindingResolutionException($message);
    }
    $this->buildStack[] = $concrete;
    // 获取构造函数
    $constructor = $reflector->getConstructor();
    // 如果构造函数是空,说明没有任何依赖,直接new返回
    if (is_null($constructor)) {
        array_pop($this->buildStack);
        return new $concrete;
    }
    
    // 获取构造函数的依赖(形参),返回一组ReflectionParameter对象组成的数组表示每一个参数
    $dependencies = $constructor->getParameters();
    $parameters = $this->keyParametersByArgument(
        $dependencies, $parameters
    );
    // 构建构造函数需要的依赖
    $instances = $this->getDependencies(
        $dependencies, $parameters
    );
    array_pop($this->buildStack);
    return $reflector->newInstanceArgs($instances);
}
//获取依赖
protected function getDependencies(array $parameters, array $primitives = [])
{
    $dependencies = [];
    foreach ($parameters as $parameter) {
        $dependency = $parameter->getClass();
        // 某一依赖值在$primitives中(即build方法的$parameters参数)已提供
        // $parameter->name返回参数名
        if (array_key_exists($parameter->name, $primitives)) {
            $dependencies[] = $primitives[$parameter->name];
        } 
        elseif (is_null($dependency)) {
             // 参数的ReflectionClass为null,说明是基本类型,如'int','string'
            $dependencies[] = $this->resolveNonClass($parameter);
        } else {
             // 参数是一个类的对象, 则用resolveClass去把对象解析出来
            $dependencies[] = $this->resolveClass($parameter);
        }
    }
    return $dependencies;
}
//解析出依赖类的对象
protected function resolveClass(ReflectionParameter $parameter)
{
    try {
        // $parameter->getClass()->name返回的是类名(参数在typehint里声明的类型)
        // 然后递归继续make(在make时发现依赖类还有其他依赖,那么会继续make依赖的依赖
        // 直到所有依赖都被解决了build才结束)
        return $this->make($parameter->getClass()->name);
    } catch (BindingResolutionException $e) {
        if ($parameter->isOptional()) {
            return $parameter->getDefaultValue();
        }
        throw $e;
    }
}

服务容器就是laravel的核心, 它通过依赖注入很好的替我们解决对象之间的相互依赖关系,而又通过控制反转让外部来来定义具体的行为(Route, Eloquent这些都是外部模块,它们自己定义了行为规范,这些类从注册到实例化给你使用才是服务容器负责的)。

一个类要被容器所能够提取,必须要先注册至这个容器。既然 laravel 称这个容器叫做服务容器,那么我们需要某个服务,就得先注册、绑定这个服务到容器,那么提供服务并绑定服务至容器的东西,就是服务提供器(ServiceProvider)。服务提供者主要分为两个部分,register(注册) 和 boot(引导、初始化)

【相关推荐:laravel视频教程

以上是laravel服务容器是什么的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
Laravel:迁移和模型之间有什么区别?Laravel:迁移和模型之间有什么区别?May 16, 2025 am 12:15 AM

迁移在Laravelmanagedatabaseschema中,同时shandledatainterAction.1)迁移术语,允许创造,修改,且deletionoftables.2)models representDataAndDataAndDataAndDataAndDataAndDataAndDataAndDataAndProvideAnInterForceNtactaction,EnablingCrudoPerations

Laravel:使用软删除或物理删除更好吗?Laravel:使用软删除或物理删除更好吗?May 16, 2025 am 12:15 AM

SoftDeletsinlaraveRareBetterTernaverainteraldatialdataAndRecoverability,而骨质骨骼验证了forderableford.fordableablefordataminimization和Privacy.1)softerdeleteseThesoftDeletDeletEstrait,允许restrestoratorralityandaudtrails和mayincroredatabasesize.2)物理

Laravel软删除:实施的综合指南Laravel软删除:实施的综合指南May 16, 2025 am 12:11 AM

softDeletsinlesinlaravelareafareafareathataLowSyOutOmarkRecordSdeletedwithOutreMovingThemfromthedataBase.toimplementsoftesoftdeletes:1)addtheSoftDeletDeletEstraittRaittRaiteRemodoyourModeNClandInClandEnCludEthedEtelet_Atcolundelet_atcolumn.2)

了解Laravel迁移:数据库架构控制变得容易了解Laravel迁移:数据库架构控制变得容易May 16, 2025 am 12:09 AM

laravelmigrationsareefectectivectiveDueTotheirversionControlandRoranderibalsible,slepliningDatabasemagementInwebDevelopment.1)heSpapsulatesCheMachangeNphpClasses,允许easyerollbacks

Laravel迁移:数据库开发的最佳实践Laravel迁移:数据库开发的最佳实践May 16, 2025 am 12:01 AM

LaravelMigrationsareArareBestWhenFollowingTheSepractices:1)用户清除,描述性formigrations,例如'addemailtouserstable'.2)ensuremigrationsareReereSareReverSiblewitha'down'method.3)考虑到the the the the the the the the the the the the the the broaderimptactondataintegnegrityAndegrityAndegrinegrityAndertality.4)optimizeperformanceb

Laravel   Vue.js 开发单页面应用(SPA)教程Laravel Vue.js 开发单页面应用(SPA)教程May 15, 2025 pm 09:54 PM

使用Laravel和Vue.js可以构建单页面应用(SPA)。1)在Laravel中定义API路由和控制器,处理数据逻辑。2)在Vue.js中创建组件化前端,实现用户界面和数据交互。3)配置CORS和使用axios进行数据交互。4)利用VueRouter实现路由管理,提升用户体验。

如何在Laravel中创建自定义辅助函数?如何在Laravel中创建自定义辅助函数?May 15, 2025 pm 09:51 PM

在Laravel中创建自定义辅助函数的步骤是:1.在composer.json中添加自动加载配置;2.运行composerdump-autoload更新自动加载器;3.在app/Helpers目录下创建并定义函数。这些函数能简化代码,提高可读性和可维护性,但需注意命名冲突和测试性。

Laravel中的数据库事务(Transactions)如何处理?Laravel中的数据库事务(Transactions)如何处理?May 15, 2025 pm 09:48 PM

在Laravel中处理数据库事务时,应使用DB::transaction方法,并注意以下要点:1.使用lockForUpdate()锁定记录;2.通过try-catch块处理异常,并在需要时手动回滚或提交事务;3.考虑事务的性能,缩短执行时间;4.避免死锁,可使用attempts参数重试事务。这段摘要完整地概括了如何在Laravel中优雅地处理事务,并提炼了文章中的核心要点和最佳实践。

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脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

螳螂BT

螳螂BT

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