Laravel’s contract is a set of interfaces that define the core services provided by the framework, such as the user watcher contract we found in the chapter introducing user authenticationIllumninateContractsAuthGuard
And user provider contractIlluminateContractsAuthUserProvider
As well as the app user model of the frameworkIlluminateContractsAuthAuthenticatable
Contract.
Why use contracts
From the source code files of the above contracts, we can see that the contracts provided by laravel are a set of interfaces defined for the core module. Laravel provides the corresponding implementation classes for each contract. The following table lists the implementation classes provided by laravel for the three contracts mentioned above.
So in my own development project, if the user authentication system provided by laravel can’t meet the requirements, you can define the implementation class of watcher and user provider according to the requirements. For example, the project I did before is that user authentication depends on the API of the company’s employee management system, so I wrote the implementation class of watcher and user provider contract by myself, so that laravel can implement the contract through self authentication Define the guard and userprovider to complete user authentication. The method of user-defined authentication has been introduced in the chapter of user authentication. Readers can browse the article.
Therefore, laravel defines contract interfaces for all core functions to enable developers to define their own implementation classes according to their own project needs. For consumers of these interfaces (such as controller or authmanager provided by kernel), they do not need to care about how the methods provided by interfaces are implemented, We only care about the functions provided by the methods of the interface, and then use these functions. We can change the implementation class for the interface when necessary according to the requirements, and the consumer side does not need to make any changes.
Defining and using contracts
What we mentioned above are all contracts provided by laravel kernel. When developing large-scale projects, we can also define contracts and implementation classes in our own projects. You may feel that the two layers of controller and model are enough for you to write code. The extra contracts and implementation classes will make the 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, we will definitely contact with the actual database.
In other words, ORM and the controller are tightly coupled. If we don’t use eloquent ORM and connect to the actual database, we can’t run or test this code. This code also violates the software design principle of “separation of concerns”.
In short: this controller knows too much.
The controller doesn’t need to know where the data comes from, just how to access it. The controller doesn’t need to know where the data is from mysql, just 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 a single responsibility, and everything in the responsibility should be encapsulated by this class
Next, we define an interface and implement it
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 has nothing to do with the data level. Here, our data may come from mysql, mongodb or redis. Our controllers don’t know and don’t need to know the difference. In this way, we can test the web layer independently of the data layer, and it will be easy to switch the storage implementation in the future.
Interface and team development
When your team is developing large-scale applications, different parts have different development speeds.
For example, one developer is working on the data layer, and another developer is working on the controller layer.
The developer who writes controller wants to test his controller, but the development of data layer is slow, so it can’t test synchronously. If two developers can reach an agreement in the way of interface first, all kinds of classes developed in the background will follow this agreement.
Once a contract is established, even if the contract is not implemented, the developer can write a “fake” implementation for the 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 the IOC container
App::singleton(‘OrderRepositoryInterface’, ‘DummyOrderRepository’);
Then the view of the application can be filled with fake data. Next, once the background developer has finished writing the real implementation code, such as redisorderrepository.
Then, with the IOC container switching interface implementation, the application can easily switch to the real implementation, and the whole application will use the data read from redis.
Interface and test
After setting up the interface agreement, it is also more conducive for us to mock when testing
public function testIndexActionBindsUsersFromRepository()
{
// Arrange...
$repository = Mockery::mock('OrderRepositoryInterface');
$repository->shouldReceive('userOrders')->once()->andReturn(['order1', 'order2]);
App::instance('OrderRepositoryInterface', $repository);
// Act...
$response = $this->action('GET', '[email protected]');
// Assert...
$this->assertResponseOk();
$this->assertViewHas('order', ['order1', 'order2']);
}
summary
Interfaces are very useful in the programming stage. In the design stage, discuss with the team what interfaces need to be made to complete the functions, and then design the specific implementation methods, input parameters and return values of each interface. Everyone can develop their own modules according to the interface conventions. When encountering interfaces that have not yet been implemented, they can completely define the fake implementation of the interface until the real implementation After the development is completed, the switch is implemented, which not only reduces the coupling between the upper layer and the lower layer in the software program structure, but also ensures that the development progress of each part does not depend too much on the completion of other parts.
So far, this article about the analysis of contracts under the laravel framework is introduced here. For more related laravel contracts, please search previous articles of developer or continue to browse the following related articles. I hope you can support developer more in the future!