13

文章转发自专业的Laravel开发者社区,原始链接:https://learnku.com/laravel/t...

学习如何用 Laravel 构建一个应用程序,不仅仅是学习使用不同的类和框架中的组件,也不是要记住全部的 artisan 命令或所有的辅助函数(我们有 Google)。学习用 Laravel 编码是学习它的哲学和优雅迷人的语法。 我个人觉得是一件艺术和工艺品(巧合的是 Laravel 工程师有时也被称作 Web 艺术家)。对其他框架这也是真理。

服务容器和 IOC 容器是 Laravel 哲学的主要部分。作为一个 Laravel 开发者,理解并能正确的使用服务容器是你掌握它的重要部分, 因为它是任何 Laravel 应用的核心。

基础

虽然 IOC 容器本质上只是一个普通的 PHP 类, 但是我喜欢将它看做"袋中的技巧"。 这个"袋子"就是我们放置或者"绑定"任何我们需要运行在 Laravel 应用中的对象服务, 从接口实现到目录路径以及其他等等。因此叫做"袋中的技巧"

现在我们拥有了一个包含所有绑定对象服务的单一对象( IOC 容器), 因此在我们的代码中,任何时候都可以很容易的从这个单一对象中获取或者"解析"这些对象服务

绑定的处理方式

现在假设我们有一个特别功能的 FooService 类。

<?php
namespace App\Services;
class FooService
{
    public function __construct()
    {
        ...
    }
    public function doSomething()
    {
        // Code for Something.
    }
}

如果我们要调用类的 doSomething 方法,我们可能会这样做 :

$fooService = new \App\Services\FooService();\
$fooService->doSomething();

这看起来没有什么问题,但比较麻烦的是这儿的 'new' 关键字,我的意思是虽然这样也很好,但是我们可以做的更优雅 (记住写代码要像 Laravel 一样,用优雅的方式)。

如何绑定 ?

绑定简单得可以用一行代码完成

$this->app->bind('FooService', \App\Services\FooService::class);

在 Laravel 中我们常说:“把 FooService 服务巧妙的注入到包中”。

当然根据使用场景和服务方式,也有其他的方法来绑定服务,只要你理解它的基本思想。有关绑定的完整参考,可以查阅 Laravel 的文档 服务容器

需要注意的是,服务必须绑定到服务提供商的注册方法中。

如何解析 ?

当服务绑定到容器之后, 我们可以在应用中的任何地方获取或者解析服务.

// 使用IoC 我们可以这么做
$fooService = app()->make('FooService');
$fooService->doSomething();
// 也可以精简为一行代码
app()->make('FooService')->doSomething();

我们只需要告诉 Laravel : "记住 FooService, 当我需要时把它给我." 你注意到了吗? 用 IoC 创建服务让代码更简洁, 明了, 易读. 这就是 Laravel 的优雅之处, 并且会使你的代码更易于测试, 因为当你测试时你可以使用一个伪造的类去替换 FooService (我觉得你应该很熟悉怎么在测试中伪造类).

在容器中绑定接口

在面向对象编程中,接口是创建一些必须遵循某种规划或者约束的类的一种方法。这能帮助其他开发者创建与您的接口中设置的约束相匹配的代码。这强制他们传递合法的参数给函数并且返回特定的数据类型,尽管他们的方法的实现可能有所不同。通过这种方式,您可以轻松的确定继承相同接口的不同实现将以相同的方式工作。

在 Laravel 的容器中我们能够绑定一个特定的接口的实现,通过这种方式,当我们解析这个接口时,我们最终会得到绑定到它的具体类。

$this->app->bind(FooInterface::class, FooClass::class);

因此,当 FooInterface 被成功解析,Laravel 足够聪明的给我们一个 FooClass 的实例。

现在想象一下我们已经写了一个 FooInterface 的更好的实现叫做 BarClass,并且我们希望用它替换 FooClass,我们所需要做的所有事情就是:

$this->app->bind(FooInterface::class, BarClass::class);

我们的代码依旧正常运行因为我们知道 BarClass 会遵循我们的接口,就算 BarClass 和预期的表现不一致我们也可以切换回 FooClass 。这是一种很好的方法,能在没有太多回归(regressions)的情况下升级应用的代码。

依赖解析

我们知道 Laravel 能够解析我们在容器中绑定的服务和接口,但是它能做的不仅仅是这些。事实上,它还能在我们一行代码都不写的情况下自动为我们解析这些服务的依赖。

想象一下我们的项目中有下面这些服务类。

<?php
class BarService 
{
  /**
   * 要做的事情。
   * 
   * @return string
   */
  public function somethingToDo()
  {
    return 'I am doing something';
  }
}
<?php
class FooService 
{
  /**
   * BarService 实例.
   * 
   * @var BarService
   */
  protected $bar;
  
  /**
   * 创建新的 FooService 实例
   * 
   * @param BarService $bar
   */
  public function __construct(BarService $bar)
  {
    $this->bar = $bar;
  }
  
  /**
   * 做点有用的事
   * 
   * @return string
   */
  public function doSomething()
  {
    return $this->bar->somethingToDo();
  }
}

我们能看到 FooService 需要一个 BarService 的实例。我们怎么才能把它绑定到容器中这样当 Laravel 给我们一个 FooService 的实例时它也会给我们一个BarService 的实例?

你或许会想这个解决方法可能会是下面这样:

$this->app->bind('Foo', new FooService(new BarService));

从技术上来说,这是可以实现的,但实际上我们不需要这样做。 Laravel 通过使用 PHP 强大的反射特性,自动就为我们解决了这个问题。因此,你只需要像往常那样绑定就好:

$this->app->bind('Foo', FooService::class);

现在,当代码解析到 Foo 时, Laravel 将会去寻找 FooService 服务,当发现它需要一个 BarService 服务的实例; Laravel 又将会去寻找 BarService 服务,并将它实例化之后的对象提供给 FooService 服务的构造函数,从而为我们创建一个完整的实例。这些过程不需要我们写一行代码,真的是十分惊艳而又清晰的思路!!

除此之外,上述过程将会提供给所有的依赖。所以,如果 BarService 服务也有自己的依赖,那么也会通过上述的方式来解决。

最后的话

关于 Laravel 的服务容器还有许多很棒的事情需要讨论和学习。我希望这个小介绍能给你一些启发并帮助你加深对容器的理解。

我鼓励你通过阅读文档了解更多  [docs]
( https://laravel.com/docs/5.6/... )。

感谢你的阅读。


summerblue
11k 声望15.4k 粉丝

刻意练习,每日精进