Contracts
Laravel의 계약은 사용자 가드 계약 IllumninateContractsAuthGuard
및 사용자 제공 계약과 같이 프레임워크에서 제공하는 핵심 서비스를 정의하는 인터페이스 세트입니다. 사용자 인증을 소개하는 장 장치 계약 IlluminateContractsAuthUserProvider
및 프레임워크와 함께 제공되는 AppUser 모델에 의해 구현됩니다. IlluminateContractsAuthAuthenticatable
계약. IllumninateContractsAuthGuard
和用户提供器契约IlluminateContractsAuthUserProvider
以及框架自带的AppUser模型所实现的IlluminateContractsAuthAuthenticatable
契约。
为什么使用契约
通过上面几个契约的源码文件我们可以看到,Laravel提供的契约是为核心模块定义的一组interface。Laravel为每个契约都提供了相应的实现类,下表列出了Laravel为上面提到的三个契约提供的实现类。
所以在自己开发的项目中,如果Laravel提供的用户认证系统无法满足需求,你可以根据需求定义看守器和用户提供器的实现类,比如我之前做的项目就是用户认证依赖于公司的员工管理系统的API,所以我就自己写了看守器和用户提供器契约的实现类,让Laravel通过自定义的Guard和UserProvider来完成用户认证。自定义用户认证的方法在介绍用户认证的章节中我们介绍过,读者可以去翻阅那块的文章。
所以Laravel为所有的核心功能都定义契约接口的目的就是为了让开发者能够根据自己项目的需要自己定义实现类,而对于这些接口的消费者(比如:Controller、或者内核提供的 AuthManager这些)他们不需要关心接口提供的方法具体是怎么实现的, 只关心接口的方法能提供什么功能然后去使用这些功能就可以了,我们可以根据需求在必要的时候为接口更换实现类,而消费端不用进行任何改动。
定义和使用契约
上面我们提到的都是Laravel内核提供的契约, 在开发大型项目的时候我们也可以自己在项目中定义契约和实现类,你有可能会觉得自带的Controller、Model两层就已经足够你编写代码了,凭空多出来契约和实现类会让开发变得繁琐。我们先从一个简单的例子出发,考虑下面的代码有什么问题:
class OrderController extends Controller { public function getUserOrders() { $orders= Order::where('user_id', '=', \Auth::user()->id)->get(); return View::make('order.index', compact('orders')); } }
这段代码很简单,但我们要想测试这段代码的话就一定会和实际的数据库发生联系。
也就是说, ORM和这个控制器有着紧耦合。如果不使用Eloquent ORM,不连接到实际数据库,我们就没办法运行或者测试这段代码。这段代码同时也违背了“关注分离”这个软件设计原则。
简单讲:这个控制器知道的太多了。
控制器不需要去了解数据是从哪儿来的,只要知道如何访问就行。控制器也不需要知道这数据是从MySQL或哪儿来的,只需要知道这数据目前是可用的。
Separation Of Concerns 关注分离
Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
每个类都应该只有单一的职责,并且职责里所有的东西都应该由这个类封装
接下来我们定义一个接口,然后实现该接口
interface OrderRepositoryInterface { public function userOrders(User $user); } class OrderRepository implements OrderRepositoryInterface { public function userOrders(User $user) { Order::where('user_id', '=', $user->id)->get(); } }
将接口的实现绑定到Laravel的服务容器中
App::singleton('OrderRepositoryInterface', 'OrderRespository');
然后我们将该接口的实现注入我们的控制器
class UserController extends Controller { public function __construct(OrderRepositoryInterface $orderRepository) { $this->orders = $orderRespository; } public function getUserOrders() { $orders = $this->orders->userOrders(); return View::make('order.index', compact('orders')); } }
现在我们的控制器就完全和数据层面无关了。在这里我们的数据可能来自MySQL,MongoDB或者Redis。我们的控制器不知道也不需要知道他们的区别。这样我们就可以独立于数据层来测试Web层了,将来切换存储实现也会很容易。
接口与团队开发
当你的团队在开发大型应用时,不同的部分有着不同的开发速度。
比如一个开发人员在开发数据层,另一个开发人员在做控制器层。
写控制器的开发者想测试他的控制器,不过数据层开发较慢没法同步测试。那如果两个开发者能先以interface的方式达成协议,后台开发的各种类都遵循这种协议。
一旦建立了约定,就算约定还没实现,开发者也可以为这接口写个“假”实现
class DummyOrderRepository implements OrderRepositoryInterface { public function userOrders(User $user) { return collect(['Order 1', 'Order 2', 'Order 3']); } }
一旦假实现写好了,就可以被绑定到IoC容器里
App::singleton('OrderRepositoryInterface', 'DummyOrderRepository');
然后这个应用的视图就可以用假数据填充了。接下来一旦后台开发者写完了真正的实现代码,比如叫RedisOrderRepository
public function testIndexActionBindsUsersFromRepository() { // Arrange... $repository = Mockery::mock('OrderRepositoryInterface'); $repository->shouldReceive('userOrders')->once()->andReturn(['order1', 'order2]); App::instance('OrderRepositoryInterface', $repository); // Act... $response = $this->action('GET', 'OrderController@getUserOrders'); // Assert... $this->assertResponseOk(); $this->assertViewHas('order', ['order1', 'order2']); }
🎜모든 클래스에는 단일 책임이 있어야 하며 해당 책임은 클래스에 의해 완전히 캡슐화되어야 합니다.🎜🎜모든 클래스에는 단일 책임만 있어야 하며 책임은 이 클래스에 의해 캡슐화되어야 합니다🎜🎜다음으로 인터페이스를 정의하고 인터페이스를 구현합니다🎜rrreee🎜인터페이스 구현을 라라벨의 서비스 컨테이너에 바인딩합니다🎜rrreee🎜그런 다음 인터페이스 구현을 주입합니다 우리의 컨트롤러🎜rrreee🎜이제 우리 컨트롤러는 데이터 레이어와 완전히 독립적입니다. 여기서 데이터는 MySQL, MongoDB 또는 Redis에서 가져올 수 있습니다. 우리 컨트롤러는 차이점을 모르고 알 필요도 없습니다. 이렇게 하면 데이터 계층과 독립적으로 웹 계층을 테스트할 수 있으며 향후 스토리지 구현을 쉽게 전환할 수 있습니다. 🎜🎜🎜인터페이스 및 팀 개발🎜🎜🎜팀이 대규모 애플리케이션을 개발할 때 부분마다 개발 속도가 다릅니다. 🎜🎜예를 들어 한 개발자는 데이터 레이어를 개발하고, 다른 개발자는 컨트롤러 레이어를 개발하고 있습니다. 🎜🎜컨트롤러를 작성한 개발자가 자신의 컨트롤러를 테스트하고 싶어하지만 데이터 레이어 개발이 느리고 동시에 테스트할 수 없습니다. 두 개발자가 먼저 인터페이스 형태로 합의하면 백그라운드에서 개발되는 모든 클래스는 이 합의를 따릅니다. 🎜🎜계약이 체결되면 계약이 아직 구현되지 않았더라도 개발자는 이 인터페이스에 대한 "가짜" 구현을 작성할 수 있습니다.🎜rrreee🎜가짜 구현이 작성되면 IoC 컨테이너에 바인딩될 수 있습니다🎜rrreee 🎜그러면 애플리케이션의 뷰가 가짜 데이터로 채워질 수 있습니다. 다음으로 백엔드 개발자가 실제 구현 코드 작성을 마치면 이를
RedisOrderRepository
라고 합니다. 🎜那么使用IoC容器切换接口实现,应用就可以轻易地切换到真正的实现上,整个应用就会使用从Redis读出来的数据了。
接口与测试
建立好接口约定后也更有利于我们在测试时进行Mock
public function testIndexActionBindsUsersFromRepository() { // Arrange... $repository = Mockery::mock('OrderRepositoryInterface'); $repository->shouldReceive('userOrders')->once()->andReturn(['order1', 'order2]); App::instance('OrderRepositoryInterface', $repository); // Act... $response = $this->action('GET', 'OrderController@getUserOrders'); // Assert... $this->assertResponseOk(); $this->assertViewHas('order', ['order1', 'order2']); }
总结
接口在程序设计阶段非常有用,在设计阶段与团队讨论完成功能需要制定哪些接口,然后设计出每个接口具体要实现的方法,方法的入参和返回值这些,每个人就可以按照接口的约定来开发自己的模块,遇到还没实现的接口完全可以先定义接口的假实现等到真正的实现开发完成后再进行切换,这样既降低了软件程序结构中上层对下层的耦合也能保证各部分的开发进度不会过度依赖其他部分的完成情况。
更多laravel框架相关技术文章,请访问laravel教程栏目!
위 내용은 Laravel 프레임워크에서 Contracts 계약 구문 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!