Laravel 服务容器、服务提供器、契约实例讲解

前言

刚开始看laravel服务容器、契约、服务提供器的确生涩难懂,不单单是概念繁多,而且实际的demo很难找(找是找到了,但难用啊),最后就隔一段时间看一遍,大概个十来遍,还真给看出个门道,废话少说上代码。

准备阶段

首先声明一下我的测试环境
php 7.2.4
laravel 5.6

1.创建新项目(composer)

在你的项目目录命令行运行:

composer create-project laravel/laravel laravelapp --prefer-dist

2.创建服务提供器

生成文件位于:项目/app/Providers/TestServiceProvider

php artisan make:provider TestServiceProvider

同时修改配置文件config/app.php,在providers数组中追加该服务提供器

'providers' => [
     App\Providers\TestServiceProvider::class,
],

3.创建自己的工作目录(xiaocai)

我的工作目录在:项目/app/Xiaocai目录下

// 目录结构  
|---app  
     |---Xiaocai    
         |---Test  
            |---DemoInterface.php  
            |---DemoProvider.php  
            |---DemoProvider2.php 

写到这里你可能问我的工作目录下三个文件干什么用呢?不要着急,接下来我们逐个创建文件并进行讲解。
1:DemoInterface.php【接口文件】这个就是laravel中提到的Contracts(契约),使用接口(契约)的原因官方也给了说明:低耦合和简单性,文件内容如下:

<?php 
namespace App\Xiaocai\Test;
interface DemoInterface
{ 
    function demo1();
    function demo2(); 
} 

接口中只简单定义两个测试方法,记住带上命名空间App\Xiaocai\Test,这个很重要;
2:DemoProvider.php【接口实现类】,这个官方并没有特别说明,当然,有了接口当然会有对应的实现类去实现接口中的方法
文件内容如下:

<?php 
namespace App\Xiaocai\Test;
class DemoProvider implements DemoInterface
{
    public function demo1()
    {
        return 'demo1';
    }
    public function demo2()
    {
        return 'demo2';
    }
}

对于这个实现类,我是这样理解的,laravel中的契约(接口)通过规定好方法名称,这样,第三方扩展包在想要实现这些契约方法的时候,必然要受到契约提供的方法的约束,防止滥用,使用户在使用laravel基础上的扩展包的时候,不必要再去了解底层代码逻辑,方法都是定义好的,尽管不同的包处理的逻辑不同,但是对于用户来说,都是调用的同一个方法,对于如何选择到底使用哪个实现类,我们也准备好了DemoProvider2.php类,他是DemoInterface.php的第二种实现方法。
3:DemoProvider2.php【接口实现类2】内容如下

<?php 
namespace App\Xiaocai\Test;
class DemoProvider2 implements DemoInterface
{
    public function demo1()
    {
        return 'demo1的第二种实现';
    }
    public function demo2()
    {
        return 'demo2的第二种实现';
    }
}

4.创建测试控制器

命令行运行:

php artisan make:controller TestController

5.添加路由

为测试控制器添加一个测试路由,修改项目/routes/web.php文件,追加代码如下:

Route::get('/test', 'TestController@index');

6.修改服务提供器文件(TestServiceProvider)

这里是重点!这里是重点!这里是重点!重要的事情我只说三遍

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Xiaocai\Test\DemoInterface;
use App\Xiaocai\Test\DemoProvider;
use App\Xiaocai\Test\DemoProvider2;

class TestServiceProvider extends ServiceProvider
{
    public function boot()
    {
    }
    public function register()
    {
        $this->app->bind(DemoInterface::class, DemoProvider2::class);
    }
}

在这里就可以对想要使用的实现接口(契约)类进行选择,$this->app->bind(arg1, arg2);参数1代表接口类,参数2代表要使用的接口实现类,你可以在这里进行切换选择实现类,切换之后如何验证呢?

验证

修改TestController文件

<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Xiaocai\Test\DemoInterface;//注意引用的是接口文件,并非实现类

class TestController extends Controller
{
    public function index(DemoInterface $demo)
    {
        dd($demo->demo1());
    }
}

此时浏览器输出接口不出意外的话应该是这样的:
file
我们不妨修改一下实现类,在TestServiceProvider文件中修改如下:

$this->app->bind(DemoInterface::class, DemoProvider::class);
//$this->app->bind(DemoInterface::class, DemoProvider2::class);

此时再运行,如下:
file

总结

如此一来,切换实现的方法是不是很简单,只需要在服务提供器中修改即可,果真是低耦合。如果文章中出现什么纰漏欢迎指出,防止祸害他人,如果觉得对您有帮助的话,点个赞支持一下吧。

结语

另外Xiaocai工作目录是测试用的,当然如果做项目的话,你也可以把你的代码上传到composer,这样通过composer直接引入到vendor目录下,通过composer的自动加载机制,这样是不是更能提高你的效率呢。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 8

关于 TestServiceProvider ,用

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Xiaocai\Test\DemoInterface;
use App\Xiaocai\Test\DemoProvider;
use App\Xiaocai\Test\DemoProvider2;

class TestServiceProvider extends ServiceProvider
{
     public $bindings = [DemoInterface::class => DemoProvider2::class];
}

其实就够了。 具体原理见 laravel 详解——03. ServiceProvider Register 解析

另外ad下,我国庆在写关于 laravel 5.7 的解析文章,欢迎关注

5年前 评论

@XiaohuiLam 嗯,这个并未进行尝试,我是延续larave的服务提供器的风格,只保留boot()和register()方法,防止他人看到突然改风格有点难以接受,不过你的方法我也会去尝试验证的,谢谢反馈。

5年前 评论

楼主,少了这个,5.5版本
vi config/app.php
App\Providers\TestServiceProvider::class,

5年前 评论

@lovecn 多谢提醒,疏漏部分已经更新。

5年前 评论

需要执行composer dumpautoload吧

5年前 评论

写的很清晰明了.谢谢楼主

4年前 评论

清晰明白, 多谢分享

4年前 评论

请问这种方式能用参数去判断bind或者singleton不同的实现类吗,比如像sdk,我要接入不同的sdk,根据参数我在boot()/register()的时候再去绑定实现类?
使用这种方式相比于我用参数去控制new一个SDK实例会更好吗?

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!