Laravel服务容器详解

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: Laravel 8 通过引入 Laravel Jetstream,模型工厂类,迁移压缩,队列批处理,改善速率限制,队列改进,动态 Blade 组件,Tailwind 分页视图, 时间测试助手,artisan serve 的改进,事件监听器的改进,以及各种其他错误修复和可用性改进,对 Laravel 7.x 继续进行了改善。

Laravel 8 通过引入 Laravel Jetstream,模型工厂类,迁移压缩,队列批处理,改善速率限制,队列改进,动态 Blade 组件,Tailwind 分页视图, 时间测试助手,artisan serve 的改进,事件监听器的改进,以及各种其他错误修复和可用性改进,对 Laravel 7.x 继续进行了改善。


服务容器介绍


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


我们来看一个简单的例子:


<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Repositories\UserRepository;
use App\Models\User;
class UserController extends Controller
{
    /**
     * user仓储的实现
     *
     * @var UserRepository
     */
    protected $users;
    /**
     * 创建一个新的控制器实例
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
    /**
     * 展示给定用户的信息
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $user = $this->users->find($id);
        return view('user.profile', ['user' => $user]);
    }
}


在这个例子中,UserController 控制器需要从数据源中获取 users。 所以,我们可以注入一个能够获取 users 的服务。在这种情况下,我们的存储仓库 UserRepository 极有可能使用 Eloquent 从数据库中获取用户信息。


然而,因为 repository 是通过 UserRepository 注入的,我们可以很轻易的将其切换为另一个实现。 另外,这种方式的便利之处也体现在:当需要为应用编写测试的时候,我们也可以很轻松地 “模拟” 或者创建一个 UserRepository 存储层的伪实现来操作。


深入理解服务容器,对于构建一个强大的、大型的应用,以及对 Laravel 核心本身的贡献都是至关重要的。


绑定


绑定基础


几乎所有的服务容器绑定都会在 服务提供者 中注册,下面示例中的大多数将演示如何在该上下文(服务提供者)中使用容器。


技巧:如果某个容器不依赖于任何接口就没必要去绑定类在这个容器里。容器不需要指定如何构建这些对象,因为它可以使用反射来自动解析这些对象。


简单绑定


在服务提供者中,我们总是可以通过 $this->app 属性访问容器。我们可以通过容器的 bind 方法注册绑定,bind 方法的第一个参数为要绑定的类 / 接口名,第二个参数是一个返回类实例的 Closure:


$this->app->bind('HelpSpot\API', function ($app) {
    return new \HelpSpot\API($app->make('HttpClient'));
});


注意,我们接受容器本身作为解析器的参数。然后,我们可以使用容器来解析正在构建的对象的子依赖。


绑定一个单例


singleton 方法将类或接口绑定到只解析一次的容器中。一旦单例绑定被解析,相同的对象实例会在随后的调用中返回到容器中:


$this->app->singleton('HelpSpot\API', function ($app) {
    return new \HelpSpot\API($app->make('HttpClient'));
});


绑定实例


我们也可以使用 instance 方法将现有对象实例绑定到容器中。给定的实例会始终在随后的调用中返回到容器中:


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


绑定接口到实现


服务容器有一个很强大的功能,就是支持绑定接口到给定的实现。例如,如果我们有个 EventPusher 接口 和一个 RedisEventPusher 实现。一旦我们写完了 EventPusher 接口的 RedisEventPusher 实现,我们就可以在服务容器中注册它,像这样:


$this->app->bind(
    'App\Contracts\EventPusher',
    'App\Services\RedisEventPusher'
);


这么做相当于告诉容器:当一个类需要实现 EventPusher 时,应该注入 RedisEventPusher。现在我们就可以在构造函数或者任何其他通过服务容器注入依赖项的地方使用类型提示注入 EventPusher 接口:


use App\Contracts\EventPusher;
/**
 * 创建一个新的类实例
 *
 * @param  EventPusher  $pusher
 * @return void
 */
public function __construct(EventPusher $pusher)
{
    $this->pusher = $pusher;
}


上下文绑定


有时我们可能有两个类使用了相同的接口,但我们希望各自注入不同的实现。


例如, 有两个控制器可能依赖了 Illuminate\Contracts\Filesystem\Filesystem 契约。


Laravel 提供了一个简单的,优雅的接口来定义这个行为:


use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });
$this->app->when([VideoController::class, UploadController::class])
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });


绑定基本值


当我们有一个类不仅需要接受一个注入类,还需要注入一个基本值(比如整数)。

我们可以使用上下文绑定来轻松注入我们的类需要的任何值:


$this->app->when('App\Http\Controllers\UserController')
          ->needs('$variableName')
          ->give($value);


有时,一个类可能依赖于一系列标记实例。使用 giveTagged 方法,

您可以轻松地使用该标签注入所有容器绑定:


$this->app->when(ReportAggregator::class)
    ->needs('$reports')
    ->giveTagged('reports');Copy


绑定变长参数类型


有时,您可能有一个使用可变参数构造函数参数接收类型化对象数组的类:


class Firewall
{
    protected $logger;
    protected $filters;
    public function __construct(Logger $logger, Filter ...$filters)
    {
        $this->logger = $logger;
        $this->filters = $filters;
    }
}


使用上下文绑定,您可以通过给 give 方法提供一个 Closure 来解决此依赖关系,该 Closure 返回已解析的 Filter 实例的数组:


$this->app->when(Firewall::class)
          ->needs(Filter::class)
          ->give(function ($app) {
                return [
                    $app->make(NullFilter::class),
                    $app->make(ProfanityFilter::class),
                    $app->make(TooLongFilter::class),
                ];
          });


为了方便起见,您还可以只提供一个类名称数组,以便在 Firewall 需要 Filter 实例时由容器解析:


$this->app->when(Firewall::class)
          ->needs(Filter::class)
          ->give([
              NullFilter::class,
              ProfanityFilter::class,
              TooLongFilter::class,
          ]);


可变依赖标记


有时,一个类可能具有可变的依赖关系,该依赖关系被类型提示为给定的类(Report ... $reports)。

使用 needs 和 giveTagged 方法,您可以轻松地为给定依赖项注入带有该标签的所有容器绑定:


$this->app->when(ReportAggregator::class)
    ->needs(Report::class)
    ->giveTagged('reports');Copy


标记


有时候,我们可能需要解析某个「分类」下的所有绑定。


比如,我们可能正在构建一个报表的聚合器,它接收一个包含不同 Report 接口实现的数组。

注册 Report 实现之后,我们可以使用 tag 方法给他们分配一个标签:


$this->app->bind('SpeedReport', function () {
    //
});
$this->app->bind('MemoryReport', function () {
    //
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');


一旦服务被标记,我们就可以通过 tagged 方法轻松地解析它们:


$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});


扩展绑定


extend 方法可以修改已解析的服务。


比如,当一个服务被解析后,我们可以添加额外的代码来修饰或者配置它。


extend 方法接受一个闭包,该闭包唯一的参数就是这个服务, 并返回修改过的服务:


$this->app->extend(Service::class, function ($service, $app) {
    return new DecoratedService($service);
});


解析


make 方法


我们可以使用 make 方法从容器中解析出类实例。make 方法接收我们想要解析的类或接口的名字:


$api = $this->app->make('HelpSpot\API');


如果我们的代码处于无法访问 $app 变量的位置,则可用全局辅助函数 resolve 来解析:


$api = resolve('HelpSpot\API');


如果类依赖不能通过容器解析,我们可以通过将它们作为关联数组作为 makeWith 方法的参数注入:


$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);


自动注入


另外,并且更重要的是,我们可以简单地使用「类型提示」 的方式在类的构造函数中注入那些需要容器解析的依赖项,包括 控制器、事件监听器、中间件 等 。


此外,我们也可以在 队列任务 的 handle 方法中使用「类型提示」注入依赖。实际上,这才是大多数对象应该被容器解析的方式。


例如,我们可以在控制器的构造函数中添加一个 repository 的类型提示,然后这个 repository 将会被自动解析并注入类中:


<?php
namespace App\Http\Controllers;
use App\Models\Users\Repository as UserRepository;
class UserController extends Controller
{
    /**
     * user 仓储实例
     */
    protected $users;
    /**
     * 创建一个控制器实例
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
    /**
     * 使用给定的 id 显示 user
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        //
    }
}


容器事件


服务容器每次解析对象会触发一个事件,我们可以使用  resolving  方法监听这个事件:


$this->app->resolving(function ($object, $app) {
    // 当容器解析任何类型的对象时调用...
});
$this->app->resolving(\HelpSpot\API::class, function ($api, $app) {
    // 当容器解析类型为 "HelpSpot\API" 的对象时调用...
});


正如我们所看到的,被解析的对象将会被传入回调函数,这使得我们能够在对象被传给调用者之前给它设置额外的属性。


PSR-11


Laravel 的服务容器实现了  PSR-11  接口。因此,我们可以使用 PSR-11 容器『接口类型提示』来获取 Laravel 容器的实例:


use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
    $service = $container->get('Service');
    //
});


如果无法解析给定的标识符,则将会引发异常。未绑定标识符时,会抛出 Psr\Container\NotFoundExceptionInterface 异常。如果标识符已绑定但无法解析,会抛出 Psr\Container\ContainerExceptionInterface 异常。

相关文章
|
3月前
|
弹性计算 Kubernetes 开发者
利用容器化服务实现游戏服务器的动态资源配置
【8月更文第12天】在游戏行业中,用户基数的变化往往呈现出明显的波动性,特别是在推广活动期间,用户基数会显著增加,而在非推广期则会有所下降。为了应对这种变化,游戏开发者需要一种能够根据用户基数动态调整服务器资源的解决方案,以确保用户体验的同时最大限度地节省成本。容器化服务因其灵活的资源管理和成本控制能力,成为了理想的解决方案。
62 2
|
4月前
|
运维 Kubernetes 开发者
构建高效后端服务:微服务架构与容器化部署的实践
【7月更文挑战第29天】 在现代软件开发中,后端服务的构建不再仅仅是代码的堆砌,而是需要考虑到可扩展性、可靠性和快速迭代等多重因素。本文将探讨如何通过微服务架构和容器化技术来构建一个高效的后端服务系统。我们将从微服务的概念出发,分析其在后端开发中的应用优势,并结合容器化技术,特别是Docker和Kubernetes的使用,来展示如何在保证服务高可用性和伸缩性的同时,简化开发和部署流程。文章还将涵盖如何应对微服务架构下的数据一致性挑战,以及如何利用现代云平台资源来优化后端服务的性能和成本效益。
|
4月前
|
Shell 应用服务中间件 nginx
docker 服务,镜像,容器命令总结
docker 服务,镜像,容器命令总结
166 4
|
3月前
|
Kubernetes 网络协议 网络安全
在K8S中,容器提供一个服务,外部访问慢,到底是容器网络问题?还是容器服务问题?这种怎么排查?
在K8S中,容器提供一个服务,外部访问慢,到底是容器网络问题?还是容器服务问题?这种怎么排查?
|
3月前
|
Kubernetes 负载均衡 网络协议
在K8S中,Pod能否实现对容器健康检查,如果服务有异常,该如何处理?
在K8S中,Pod能否实现对容器健康检查,如果服务有异常,该如何处理?
|
6月前
|
Kubernetes 负载均衡 开发者
构建高效后端服务:从微服务到容器化部署
【5月更文挑战第31天】本文深入探讨了现代后端开发中的关键概念,包括微服务的架构设计、容器化技术的运用以及它们如何共同提升应用的可扩展性、可靠性和性能。通过具体案例分析,我们将揭示这些技术是如何在实际开发中被实施的,并讨论它们对后端开发流程的影响。
|
6月前
|
Docker 容器
【开发问题记录】启动某个服务时请求失败(docker-componse创建容器时IP参数不正确)
【开发问题记录】启动某个服务时请求失败(docker-componse创建容器时IP参数不正确)
80 1
|
9天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
38 2
|
19天前
|
Kubernetes 监控 开发者
掌握容器化:Docker与Kubernetes的最佳实践
【10月更文挑战第26天】本文深入探讨了Docker和Kubernetes的最佳实践,涵盖Dockerfile优化、数据卷管理、网络配置、Pod设计、服务发现与负载均衡、声明式更新等内容。同时介绍了容器化现有应用、自动化部署、监控与日志等开发技巧,以及Docker Compose和Helm等实用工具。旨在帮助开发者提高开发效率和系统稳定性,构建现代、高效、可扩展的应用。
|
15天前
|
关系型数据库 MySQL API