最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(11) Laravel服务容器

    正文概述 转载于:掘金(王中阳Go)   2021-08-25   201

    这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

    之前写了一篇Laravel提高DB查询效率的文章,转发到群里后竟然有人质疑我说“Laravel是他好几年前用的框架,没想到现在还有人在用。”

    纳尼,什么意思嘛?别忘了php是最好的语言!

    个人认为Laravel是非常优雅的开发框架:优雅的设计模式、强大的功能实现、各种方便的扩展、持续的版本更新,更主要的是迄今为止我认为最优秀的技术开发社区。

    我必须为Laravel打Call。

    2020年9月8号,Laravel发布了8.0版本。 Laravel计划于2022年1月25日发布9.0版本。

    下面我介绍一下目前Laravel最新版(8.0版本)的新特性:

    服务容器介绍

    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 异常。

    华丽的分割线

    要了解更多有关在Laravel的知识点,请查看我的专栏: 服务端开发从入门到精通

    推荐阅读

    1. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(1)Laravel如何优雅的设置全局变量

    2. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(2)Laravel Jetstream和模型工厂类

    3. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(3)迁移压缩,队列批处理,改善速率限制

    4. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(4)维护模式优化

    5. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(5) 动态Blade 事件监听器优化 事件测试助手

    6. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(6)

    7. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(7)Laravel安装指南

    8. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(8) 目录结构介绍

    9. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(9) Laravel的部署

    10. 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(10) Laravel的请求周期介绍

    Last but not least

    技术交流群请到 这里来。 或者添加我的微信 wangzhongyang0601 ,一起学习。


    起源地 » 竟然有人质疑我还在用Laravel开发?别忘了PHP是最好的语言。(11) Laravel服务容器

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元