Home  >  Article  >  PHP Framework  >  Parsing the Contracts contract under the Laravel framework

Parsing the Contracts contract under the Laravel framework

藏色散人
藏色散人forward
2020-01-31 20:08:141985browse

Parsing the Contracts contract under the Laravel framework

Contracts

Laravel’s contract is a set of interfaces that define the core services provided by the framework. For example, we have mentioned in the chapter introducing user authentication The User Guard ContractIllumninateContractsAuthGuard and the User Provider ContractIlluminateContractsAuthUserProvider as well as the IlluminateContractsAuthAuthenticatable# implemented by the App\User model that comes with the framework ##contract.

Why use contracts

We can see from the source code files of the above contracts that the contract provided by Laravel is a set of interfaces defined for the core module . Laravel provides corresponding implementation classes for each contract. The following table lists the implementation classes provided by Laravel for the three contracts mentioned above.

Parsing the Contracts contract under the Laravel framework

So in your own development project, if the user authentication system provided by Laravel cannot meet your needs, you can define the implementation classes of the guard and user provider according to your needs, such as The project I worked on before was that user authentication relied on the API of the company's employee management system, so I wrote the implementation class of the guard and user provider contract myself, and let Laravel complete user authentication through the custom Guard and UserProvider. We have introduced the method of customizing user authentication in the chapter introducing user authentication. Readers can read that article.

So the purpose of Laravel defining contract interfaces for all core functions is to allow developers to define their own implementation classes according to the needs of their own projects, and for consumers of these interfaces (such as: Controller, or the kernel provides AuthManager, etc.) They do not need to care about how the methods provided by the interface are implemented. They only care about what functions the interface methods can provide and then use these functions. We can change the implementation class for the interface when necessary according to needs. There is no need to make any changes on the consumer side.

Define and use contracts

What we mentioned above are all contracts provided by the Laravel kernel. When developing large projects, we can also define contracts in the project ourselves. and implementation classes. You may feel that the built-in Controller and Model layers are enough for you to write code. Adding additional contracts and implementation classes out of thin air will make development cumbersome. Let's start with a simple example and consider what's wrong with the following code:

class OrderController extends Controller
{
    public function getUserOrders()
    {
        $orders= Order::where('user_id', '=', \Auth::user()->id)->get();
        return View::make('order.index', compact('orders'));
    }
}

This code is very simple, but if we want to test this code, it will definitely be in contact with the actual database.

In other words, the ORM and this controller are tightly coupled. We have no way to run or test this code without using the Eloquent ORM and connecting to an actual database. This code also violates the software design principle of "separation of concerns".

To put it simply: this controller knows too much.

The controller does not need to know where the data comes from, as long as it knows how to access it. The controller doesn't need to know where the data comes from MySQL, it just needs to know that the data is currently available.

Separation Of Concerns

Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.

Each class should have only a single responsibility, and everything in the responsibility should be encapsulated by this class

Next we define an interface and then implement the interface

interface OrderRepositoryInterface 
{
    public function userOrders(User $user);
}
 
class OrderRepository implements OrderRepositoryInterface
{
    public function userOrders(User $user)
    {
        Order::where('user_id', '=', $user->id)->get();
    }
}

Bind the implementation of the interface to Laravel's service container

App::singleton('OrderRepositoryInterface', 'OrderRespository');

Then we inject the implementation of the interface into our controller

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'));
    }
}

Now our controller is completely independent of the data layer . Here our data may come from MySQL, MongoDB or Redis. Our controller doesn't know and doesn't need to know the difference. This way we can test the web layer independently of the data layer, and it will be easy to switch storage implementations in the future.

Interface and Team Development

When your team is developing a large application, different parts have different development speeds.

For example, one developer is developing the data layer, and another developer is working on the controller layer.

The developer who wrote the controller wants to test his controller, but the data layer development is slow and cannot be tested simultaneously. If two developers can first reach an agreement in the form of an interface, various classes developed in the background will follow this agreement.

Once the agreement is established, even if the agreement has not been implemented, the developer can also write a "fake" implementation for this interface

class DummyOrderRepository implements OrderRepositoryInterface 
{
    public function userOrders(User $user)
    {
        return collect(['Order 1', 'Order 2', 'Order 3']);
    }
}

Once the fake implementation is written, it can be bound to In the IoC container

App::singleton('OrderRepositoryInterface', 'DummyOrderRepository');

Then the view of this application can be filled with fake data. Next, once the backend developer has finished writing the real implementation code, for example, it is called

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教程栏目!

The above is the detailed content of Parsing the Contracts contract under the Laravel framework. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:cnblogs.com. If there is any infringement, please contact admin@php.cn delete