Laravel框架的基石就是一个功能强大的 IoC 容器

好记性不如烂笔头,学习php开发也不能懒,作笔记是一种学习的好习惯!
文章来自:mp.weixin.qq.com/s/2zo7d1wb9mpweHw...
学习与交流:Laravel技术交流微信群

整个 Laravel 框架的基石是一个功能强大的 IoC 容器(控制反转容器),如果你想真正从底层理解 Laravel 框架,就必须好好掌握它。不过,也不要被这个吓住,要知道 IoC 容器只不过是一种用于方便我们实现「依赖注入」这种软件设计模式的工具。而且要实现依赖注入并不一定非要通过 IoC 容器,只是使用 IoC 容器会更容易一点。


首先,来看看我们为何要使用依赖注入,或者说它能为我们的软件开发带来什么好处。考虑下列代码中的类和方法:

class UserController extends BaseController
{
    public function getIndex()
    {
        $users = User::all();
        return View::make('users.index', compact('users'));
    }
}

这段代码看起来很简洁,但是不与数据库打交道的话,我们将无法测试这段代码。

也就是说,Eloquent ORM 和该控制器有着紧耦合关系。如果不使用 Eloquent ORM,不连接到实际数据库,我们就没办法运行或者测试这段代码。同时,这段代码也违背了「关注点分离」这个软件设计原则。


简单来讲:控制器知道的太多了。控制器不需要去了解数据是从哪儿来的,只要知道如何访问就行。控制器也不需要知道数据在 MySQL 中是否有效,只需要知道它目前是可用的。

所以,如果可以完全解耦 Web 控制器层和数据访问层解耦,将会给我们带来诸多便利:这会使得迁移数据存储实现更容易;也会使得代码测试更容易。

「Web控制器」的职责就是真实应用的传输层:仅负责收集用户请求数据,然后将其传递给处理方。

假设你有一个类似于监控器的应用程序,该应用有很多线缆接口,你可以通过这些接口来访问监控器的功能,接口包括 HDMI,VGA,DVI 等。把互联网想象成另一个插进应用的线缆接口,显示器的大部分功能都是与线缆接口无关的、互相独立的。线缆接口只是一种传输机制,就像 HTTP 只是你程序的一种传输机制一样。

所以,我们不想把传输机制(控制器)和业务逻辑混在一起。这样做的好处是很多其他的传输层比如 API 接口、移动 App 等都可以访问我们的业务逻辑。

因此,以后开发代码就别再将控制器和 Eloquent ORM 耦合在一起了,咱们来注入一个仓库类吧。


建立约定

首先,我们来定义一个接口,然后实现该接口。

interface UserRepositoryInterface
{
    public function all(): array;
}

class DbUserRepository implements UserRepositoryInterface
{
    public function all(): array
    {
        return User::all()->toArray();
    }
}

然后,我们将该接口的实现注入到我们的控制器

class UserController extends BaseController
{
    public function __construct(UserRepositoryInterface $users)
    {
        $this->users = $users;
    }

    public function getIndex()
    {
        $users=$this->users->all();
        return View::make('users.index', compact('users'));
    }
}

现在,我们的控制器就完全不知道数据存储在哪了。在这里,无知是福!我们的数据可能来自 MySQL、MongoDB 或者 Redis,我们的控制器不知道也不需要知道到底用的是什么数据库,以及它们是如何存储数据的,在具体实现上有什么区别。

仅仅做出了这么小小的改变,我们就可以独立于数据层来测试 Web 层了,将来如果需要的话,切换存储实现也会很容易,两者相互独立,只要调用方法名不改,我们的控制器代码不用做任何改动。

严守边界:始终牢记保持明确的责任边界,控制器和路由是作为 HTTP 和应用程序之间的中介者来提供服务的(用户浏览应用的时候,路由/控制器作为中介将其引导到对应的服务)。当编写大型应用程序时,不要将你的领域逻辑混杂在控制器或路由中。

为了巩固你对这一理念的理解,我们来写一个测试案例。首先,我们要通过 Mockery 动态模拟一个仓库类实例,并将其绑定到应用的 IoC 容器里。然后,发起一个请求,通过断言判定控制器是否正确地调用了这个仓库类:

public function testUserTest()
{
    $repository = \Mockery::mock(UserRepositoryInterface::class);
    $repository->shouldReceive('all')->once()->andReturn(['LetsFeng']);
    $this->instance(UserRepositoryInterface::class, $repository);
    $response = $this->get('/users');

    $response->assertStatus(200);
    $response->assertViewHas('users', ['LetsFeng']);
}

运行结果如下:

图片


更进一步

让我们考虑另一个例子来巩固理解。当付费会员订阅的某项服务周期快结束了,可能需要去提醒用户该续费了。

我们会定义两个接口,或者叫契约(这些契约使我们在更改实际实现时更加灵活),一个是支付接口,一个是通知接口

interface BillerInterface 
{
    public function bill(array $user, $amount);
}

interface BillingNotifierInterface 
{
    public function notify(array $user, $amount);
}

接下来我们要写一个 BillerInterface 接口的实现

class StripeBiller implements BillerInterface
{
    public function __construct(BillingNotifierInterface $notifier)
    {
        $this->notifier = $notifier;
    }
    public function bill(array $user, $amount)
    {
        // Bill the user via Stripe...
        $this->notifier->notify($user, $amount);
    }
}

通过将责任划分到不同类中,我们现在可以很容易将不同的通知实现类注入到账单类里面。

比如,我们可以注入一个 SmsNotifier 或者 EmailNotifier。账单类只需遵守了自己的契约即可(实现了账单接口方法),不需要考虑如何实现通知功能。只要是遵守账单通知契约(接口)的类,账单类都可以用。

这不仅让我们的开发维护更加灵活,而且还可以通过模拟BillingNotifierInterface 实现类来进行账单类的隔离测试,就像我们在上一个测试用例里做的那样。


面向接口开发:编写接口看上去好像要多写一些代码,但是磨刀不误砍柴工,对于大型项目而言实际上反而能提升你的开发效率,这就是软件设计领域经常说的面向接口开发,而不是面向对象开发。从测试角度来说,你不用实现任何接口,就能通过 Mockery 库模拟接口实现实例,进而测试整个后端逻辑!


前面说了这么多,回到我们的主题,我们要如何做依赖注入呢?很简单:

$biller = new StripeBiller(new SmsNotifier);

这就是一个依赖注入。账单类 StripeBiller 不用考虑如何通知用户,我们直接传递给它一个通知实现类 SmsNotifier 的实例。

从代码角度来说,这可能只是个微小的变动,但这种设计模式的引入,绝对会使你的整个应用架构焕然一新:因为明确指定了类的职责边界,实现了不同层和服务之间的解耦,你的代码变得更加容易维护;

此外,从面向接口编程的角度来看,代码变得更加容易测试,你只需通过模拟注入依赖即可,不同类之间的测试完全可以隔离开来。

本作品采用《CC 协议》,转载必须注明作者和本文链接
程序员的福利:免费获取 JetBrains 全家桶激活码 推荐:【点击这里获取 。。。激活码适用:AppCode, CLion, DataGrip, DataSpell, dotCover, dotMemory, dotTrace, goland, IntelliJ IDEA Ultimate, phpstorm, pycharm, ReSharper
Laravel00
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 1

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
资深程序猿 @ XX科技
文章
62
粉丝
37
喜欢
249
收藏
573
排名:282
访问:3.3 万
私信
所有博文
社区赞助商