The foundation of the Laravel framework is its powerful IoC container. To truly understand the framework, a strong grasp of the container is necessary. However, we shold note that an IoC container is simply a convenience mechanism for achieving a software design pattern: dependency injectionl. A container is not necessary to perform dependency injection, it simply makes the task easier.
Laravel框架的基 础是一个功能强大的控制反转容器(IoC container)。 为了真正理解本框架,需要好好掌握该容器。然而我们需要了解,控制反转容器只是一种用于方便实现“依赖注入”的工具。但要实现依赖注入并不一定需要控制反 转容器,只是用容器会更方便和容易一点儿。
First, let's explore why dependency injection is beneficial. Consider the following class and method:
首先来看看我们为何要使用依赖注入,它能带来什么好处。 考虑下列代码:
class UserController extends BaseController{ public function getIndex() { $users= User::all(); return View::make('users.index', compact('users')); } }
While this code is concise, we are unable to test it without hitting an actual database. In other words, the Eloquent ORM is tightly coupled to out controller. We have no way to use or test this controller without also using the entire Eloquent ORM, including hitting a live database. This code also violates a software design principle commonly called separation of concerns. Simple put: our controller knows too much. Controllers do not need to know where data comes from, but only how to access it. The controller doesn't need to know that the data is available in MySQL, but only that it is available somewhere.
这段代码很 简短,但我们要想测试这段代码的话就一定会和实际的数据库发生联系。也就是说, Eloquent ORM(译者注:Laravel的数据库对象模型库)和该控制器有着紧耦合。如果不使用Eloquent ORM,不连接到实际数据库,我们就没办法运行或者测试这段代码。这段代码同时也违背了“关注分离”这个软件设计原则。简单讲:这个控制器知道的太多了。 控制器不需要去了解数据是从哪儿来的,只要知道如何访问就行。控制器也不需要知道这数据是从MySQL或哪儿来的,只需要知道这数据目前是可用的。
Separation Of Concerns 关注分离
Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
每一个类都应该有单独的职责,并且该职责应完全被这个类封装。(译者注:我认为就是不要让多个类负责同样的职责)
So, it will be beneficial for us to decouple our web layer (controller) from our data access layer completely. This will allow us to migrate storage implementations easily, as well as make our code easier to test. Think of the "Web" as just a transport layer into your "real" application.
关注分离的好处就是能让Web控制器和数据访问解耦。这会使得实现存储迁移更容易,测试也会更容易。“Web”就仅仅是为你真正的应用做数据的传输了。
Imagine that your application is like a monitor with a variety of cable ports. You can access the monitor's functionality via HDMI, VGA, or DVI. Think of the Internet as just a cable into your application. The bulk of a monitor's functionality is independent of the cable. The cable is just a transport mechanism just like HTTP is a transport mechanism for your application. So, we don't want to clutter up our transport mechanism (the controller) with application logic. This will allow any transport layer, such as an API or mobile application, to access our application logic.
想象一下你有一个类似于监视器的程序,有着很多线缆接口(HDMI,VGA,DVI等等)。 你可以通过不同的接口访问不同的监视器。把Internet想象成另一个插进你程序线缆接口。大部分显示器的功能是与线缆接口互相独立的。线缆接口只是一 种传输机制就像HTTP是你程序的一种传输机制一样。所以我们不想把传输机制(控制器)和业务逻辑混在一起。这样的好处是很多其他的传输机制比如API调 用、移动应用等都可以访问我们的业务逻辑。
So, instead of coupling our controller to the Eloquent ORM, let's inject a repository class.
那么我们就别再将控制器和Eloquent ORM耦合在一起了。 咱们注入一个资料库类。
First, we'll define an interface and a corresponding implementation:
首先我们定义一个接口,然后实现该接口。
interface UserRepositoryInterface { public function all(); } class DbUserRepository implements UserRepositoryInterface { public function all() { return User::all()->toArray(); } }
Next, we'll inject an implementation of this interface into our controller:
然后我们将该接口的实现注入我们的控制器。
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')); } }
Now our controller is completely ignorant of where our user data is being stored. In this case, ignorance is bless! Our data could be coming from MySQL, MongoDB, or Redis. Our controller doesn't know the difference, nor should it care. Just by making this small change, we can test our web layer independent of our data layer, as well as easily switch our storage implementation.
现在我们的控制器就完全和数据层面无关了。在这里无知是福!我们的数据可能来自MySQL,MongoDB或者Redis。我们的控制器不知道也不需要知道他们的区别。仅仅做出了这么小小的改变,我们就可以独立于数据层来测试Web层了,将来切换存储实现也会很容易。
Respect Boundaries 严守边界
Remember to respect responsibility boundaries. Controllers and routes serve as a mediator between HTTP and your application. When writing large applications, don't clutter them up with your domain logic.
记得要保持清晰的责任边界。 控制器和路由是作为HTTP和你的应用程序之间的中间件来用的。当编写大型应用程序时,不要将你的领域逻辑混杂在其中(控制器、路由)。
To solidify our understanding, let's write a quick test. First, we'll mock the repository and bind it to the application IoC container. Then, we'll ensure that the controller properly calls the repository:
为了巩固学到的知识,咱们来写一个测试案例。首先,我们要模拟一个资料库然后绑定到应用的IoC容器里。 然后,我们要保证控制器正确的调用了这个资料库:
public function testIndexActionBindsUsersFromRepository() { // Arrange... $repository = Mockery::mock('UserRepositoryInterface'); $repository->shouldReceive('all')->once()->andReturn(array('foo')); App::instance('UserRepositoryInterface', $repository); // Act... $response = $this->action('GET', 'UserController@getIndex'); // Assert... $this->assertResponseOk(); $this->assertViewHas('users', array('foo')); }
Are you Mocking Me 你在模仿我么?
In this example, we used the `Mockery` mocking library. This library provides a clean, expressive interface for mocking your classes. Mockery can be easily insalled via Composer.
在上面的例子里, 我们使用了名为`Mockery`的模仿库。 这个库提供了一套整洁且富有表达力的方法,用来模仿你写的类。 Mockery可以通过Composer安装。