Laravel服务容器与服务提供者

好的,各位观众,掌声响起来!今天,咱们要聊聊Laravel这座城堡里,两个至关重要的角色:服务容器(Service Container)和服务提供者(Service Provider)。

想象一下,Laravel 是一个庞大的城市,而服务容器就像是这个城市的中央调度中心,它负责管理各种资源、组件,并确保它们能够高效协同工作。服务提供者呢?它们就像是这座城市的各个供应商,专门负责提供特定类型的服务,比如数据库连接、邮件发送、缓存等等。

一、服务容器:Laravel 的大脑

服务容器,也常被亲切地称为 IoC 容器(Inversion of Control Container),这名字听起来有点高大上,其实它干的活儿很简单:管理类的依赖关系并注入它们。 换句话说,它负责创建对象,并把对象所需要的依赖项“喂”给它。

传统的编程方式,我们创建一个对象,往往需要手动解决它的依赖关系,就像这样:

class ArticleController
{
    protected $articleRepository;

    public function __construct()
    {
        $this->articleRepository = new ArticleRepository(new DatabaseConnection());
    }

    public function index()
    {
        $articles = $this->articleRepository->getAll();
        // ...
    }
}

这样写有什么问题呢?

  • 耦合度高: ArticleController 紧紧依赖于 ArticleRepositoryDatabaseConnection。如果要更换数据库连接方式,或者使用其他的 Repository 实现,就必须修改 ArticleController 的代码。
  • 难以测试: 单元测试 ArticleController 时,很难 mock 掉 ArticleRepositoryDatabaseConnection,因为它们是在构造函数里直接 new 出来的。

服务容器的出现,就是为了解决这些问题。它把对象的创建和依赖关系的解析工作,从类自身转移到了容器中。

class ArticleController
{
    protected $articleRepository;

    public function __construct(ArticleRepository $articleRepository)
    {
        $this->articleRepository = $articleRepository;
    }

    public function index()
    {
        $articles = $this->articleRepository->getAll();
        // ...
    }
}

现在,ArticleController 只需要声明它需要 ArticleRepository,而不需要关心 ArticleRepository 是如何创建的,以及它的依赖项是什么。这些都交给服务容器来处理。

服务容器的优势:

  • 解耦: 类之间的依赖关系更加松散,易于维护和修改。
  • 可测试性: 可以轻松地 mock 掉依赖项,进行单元测试。
  • 可重用性: 可以将服务注册到容器中,并在多个地方复用。
  • 灵活性: 可以根据不同的环境或配置,绑定不同的实现。

服务容器的使用方法:

Laravel 提供了多种方式来与服务容器交互。

  1. 绑定(Binding): 将接口或类与具体的实现关联起来。

    • bind() 方法: 绑定一个接口到一个闭包,每次从容器中解析该接口时,都会执行该闭包。

      $this->app->bind(ArticleRepositoryInterface::class, function ($app) {
          return new ArticleRepository(new DatabaseConnection());
      });
    • singleton() 方法: 绑定一个接口到一个闭包,但只会执行一次,后续每次从容器中解析该接口时,都会返回同一个实例。

      $this->app->singleton(DatabaseConnection::class, function ($app) {
          return new DatabaseConnection();
      });
    • instance() 方法: 绑定一个接口到一个已存在的实例。

      $connection = new DatabaseConnection();
      $this->app->instance(DatabaseConnection::class, $connection);
  2. 解析(Resolving): 从容器中获取一个类的实例。

    • make() 方法: 从容器中解析一个类。

      $articleRepository = $this->app->make(ArticleRepositoryInterface::class);
    • 类型提示: 在构造函数或方法参数中使用类型提示,Laravel 会自动从容器中解析依赖项。

      public function __construct(ArticleRepositoryInterface $articleRepository)
      {
          $this->articleRepository = $articleRepository;
      }

服务容器的别名(Alias):

为了方便使用,可以为服务容器中的绑定设置别名。

$this->app->alias(ArticleRepository::class, 'article');

// 使用别名解析
$articleRepository = $this->app->make('article');

二、服务提供者:服务的搬运工

服务提供者是 Laravel 应用程序启动的中心场所。 Laravel 应用程序的大部分核心引导工作都是通过服务提供者来完成的。 它们负责:

  • 注册服务: 将服务绑定到服务容器中。
  • 启动服务: 执行一些初始化操作,比如注册事件监听器、注册中间件、定义路由等等。

可以将服务提供者想象成一个个模块化的插件,它们可以很容易地添加到 Laravel 应用程序中,或者从应用程序中移除。

服务提供者的结构:

一个服务提供者通常包含两个方法:

  • register() 方法: 用于注册服务到服务容器中。这个方法应该只负责绑定服务,不要尝试启动任何服务或依赖任何其他的服务。
  • boot() 方法: 用于启动服务。在这个方法中,可以访问所有的服务,包括 Laravel 提供的核心服务。
namespace AppProviders;

use IlluminateSupportServiceProvider;
use AppRepositoriesArticleRepository;
use AppRepositoriesArticleRepositoryInterface;

class ArticleServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(ArticleRepositoryInterface::class, ArticleRepository::class);
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        // 注册事件监听器
        // Event::listen('article.created', 'AppListenersArticleCreatedListener');

        // 发布配置文件
        // $this->publishes([
        //     __DIR__.'/../config/article.php' => config_path('article.php'),
        // ]);
    }
}

注册服务提供者:

需要在 config/app.php 文件中的 providers 数组中注册服务提供者。

'providers' => [
    // ...
    AppProvidersArticleServiceProvider::class,
],

延迟加载服务提供者:

如果一个服务提供者只在特定的情况下才需要加载,可以将其设置为延迟加载。这样可以提高应用程序的启动速度。

class ArticleServiceProvider extends ServiceProvider
{
    protected $defer = true;

    public function register()
    {
        $this->app->bind(ArticleRepositoryInterface::class, ArticleRepository::class);
    }

    public function provides()
    {
        return [ArticleRepositoryInterface::class];
    }
}

$defer = true; 表示该服务提供者是延迟加载的。

provides() 方法返回该服务提供者提供的服务。 Laravel 只会在需要这些服务时,才会加载该服务提供者。

服务提供者的种类:

Laravel 自带了很多服务提供者,负责启动框架的各个核心组件。

  • AppServiceProvider 应用程序级别的服务提供者,用于注册应用程序特定的服务。
  • AuthServiceProvider 用于定义授权策略。
  • BroadcastServiceProvider 用于注册广播频道。
  • EventServiceProvider 用于注册事件监听器。
  • RouteServiceProvider 用于定义路由。

当然,你也可以创建自定义的服务提供者,来管理自己的服务。

三、实战演练:打造一个自定义的服务

现在,让我们来创建一个自定义的服务,并使用服务容器和服务提供者来管理它。

场景: 我们需要一个服务,用于生成文章的摘要。

  1. 创建摘要生成器接口:

    namespace AppServices;
    
    interface SummaryGeneratorInterface
    {
        public function generate(string $content, int $length = 100): string;
    }
  2. 创建摘要生成器实现:

    namespace AppServices;
    
    class SummaryGenerator implements SummaryGeneratorInterface
    {
        public function generate(string $content, int $length = 100): string
        {
            // 简单的摘要生成逻辑
            $content = strip_tags($content);
            if (strlen($content) > $length) {
                $content = substr($content, 0, $length) . '...';
            }
            return $content;
        }
    }
  3. 创建服务提供者:

    namespace AppProviders;
    
    use IlluminateSupportServiceProvider;
    use AppServicesSummaryGenerator;
    use AppServicesSummaryGeneratorInterface;
    
    class SummaryGeneratorServiceProvider extends ServiceProvider
    {
        /**
         * Register services.
         *
         * @return void
         */
        public function register()
        {
            $this->app->bind(SummaryGeneratorInterface::class, SummaryGenerator::class);
        }
    
        /**
         * Bootstrap services.
         *
         * @return void
         */
        public function boot()
        {
            // 可以发布配置文件,或者注册命令等
        }
    }
  4. 注册服务提供者:

    config/app.php 文件中注册 SummaryGeneratorServiceProvider

  5. 使用摘要生成器:

    namespace AppHttpControllers;
    
    use AppHttpControllersController;
    use AppServicesSummaryGeneratorInterface;
    
    class ArticleController extends Controller
    {
        protected $summaryGenerator;
    
        public function __construct(SummaryGeneratorInterface $summaryGenerator)
        {
            $this->summaryGenerator = $summaryGenerator;
        }
    
        public function show($id)
        {
            $article = Article::findOrFail($id);
            $summary = $this->summaryGenerator->generate($article->content, 200);
    
            return view('article.show', compact('article', 'summary'));
        }
    }

现在,ArticleController 就可以通过类型提示,从服务容器中获取 SummaryGenerator 的实例,并使用它来生成文章的摘要了。

四、总结:服务容器与服务提供者,珠联璧合

服务容器和服务提供者,是 Laravel 框架中两个非常重要的概念。 它们一起工作,为我们提供了一种优雅、灵活的方式来管理应用程序的依赖关系和服务。

特性 服务容器 服务提供者
作用 管理类的依赖关系并注入它们。 注册服务到服务容器中,并启动服务。
核心方法 bind(), singleton(), make() register(), boot()
优点 解耦、可测试性、可重用性、灵活性。 模块化、易于扩展、延迟加载。
比喻 城市的中央调度中心。 城市的各个供应商。
适用场景 需要解耦和管理依赖关系的地方。 需要注册和启动服务的地方。

掌握了服务容器和服务提供者,就掌握了 Laravel 的核心思想,可以更加轻松地构建可维护、可扩展的应用程序。

希望今天的讲解对大家有所帮助! 谢谢大家! 👏

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注