The content of this article is about the analysis of Contracts under the Larave framework. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
Laravel's contracts are a set of interfaces that define the core services provided by the framework, such as the user guard contract IllumninateContractsAuthGuard we saw in the chapter introducing user authentication. And the user provider contract IlluminateContractsAuthUserProvider
Through the source code files of the above contracts, we can see 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.
Contract |
Implementation class provided by Laravel kernel |
IllumninateContractsAuthGuard | IlluminateAuthSessionGuard |
IlluminateContractsAuthUserProvider |
IlluminateAuthEloquentUserProvider |
##IlluminateContractsAuthAuthenticatable | IlluminateFoundationAuthAuthenticatable(parent class of User Model ) |
So in your own development project, if the user authentication system provided by Laravel cannot meet the needs, you can define the implementation classes of the guard and user provider according to the needs. For example, the project I worked on before was that the user authentication relied on the company. The API of the employee management system, so I wrote the implementation classes of the guard and user provider contracts myself, allowing Laravel to complete user authentication through the customized 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". Simply put: this controller knows too much. The controller does not need to know where the data comes from, just 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.
Every class should have only a single responsibility Responsibilities, and everything in the responsibilities 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 this 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, developers 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 the IoC container
App::singleton('OrderRepositoryInterface', 'DummyOrderRepository');
Then the application’s views can be populated with fake data. Next, once the backend developer has finished writing the real implementation code, such as called RedisOrderRepository. Then using the IoC container switching interface implementation, the application can easily switch to the real implementation, and the entire application will use the data read from Redis.
Interface and Testing
After establishing the interface agreement, it will be more conducive for us to mock during 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', 'OrderController@getUserOrders');
// Assert...
$this->assertResponseOk();
$this->assertViewHas('order', ['order1', 'order2']);
}
Summary
Interfaces are very useful in the programming stage. During the design stage, discuss with the team which interfaces need to be developed to complete the function, and then design the specific methods to be implemented for each interface, the input parameters and return values of the methods, etc., everyone You can develop your own module according to the interface agreement. When you encounter an interface that has not yet been implemented, you can first define a fake implementation of the interface and wait until the real implementation development is completed before switching. This not only reduces the impact of the upper layer on the lower layer in the software program structure. Coupling also ensures that the development progress of each part is not overly dependent on the completion of other parts.
The above is the detailed content of Analysis of Contracts under the Larave framework. For more information, please follow other related articles on the PHP Chinese website!