Core concept notes of Laravel: service container

notes

Original link: https://xueyuanjun.com/post/5805

1 Introduction

The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. The essence of dependency injection is to inject the class dependency into the class through the constructor or in some cases through the set method.
Example:

<?php

namespace App\Http\Controllers;

use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * The user repository implementation.
     *
     * @var UserRepository
     */
    protected $users;

    /**
     * Create a new controller instance.
     * 
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $user = $this->users->find($id);
        return view('user.profile', ['user' => $user]);
    }
}

UserController needs to get users from data source, so we inject a service UserRepository that can get users. After injecting user repository, other implementations are encapsulated on the basis of it, and fake urs can also be simulated for testing.

2 binding

Binding basis

Almost all binding is done in the service provider.
Note: if a class is not based on any excuse, there is no need to bind it to a container. The container doesn't need to be told how to build objects, because it automatically parses specific objects using PHP's reflection service.
Simple binding
In a service provider, you can access the container through the $this - > app variable, and then use the bind method to register a binding. This method requires two parameters. The first parameter is the name of the class or interface we want to register, and the second parameter is the closure of the instance of the returned class:

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

Notice that we use the container itself as a parameter to the parser, and then we use it to parse the object we are building.
Bind a single instance
The singleton method binds a class or interface that only needs to be parsed once to the container, and then the next call to the container will return the same instance:

$this->app->singleton('FooBar', function ($app) {
    return new FooBar($app->make('HttpClient'));
});

Bind instance
You can also use the instance method to bind an existing object instance to the container, and then the container will always return the given instance:

$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);

Bind original value
Suppose that there is a class that receives the injection class and needs to inject a native value such as integer. You can easily inject any value needed by this class in combination with the context:

$this->app->when('APP\Http\Controllers\UserController')
	->needs('$variableName')
	->give($value)

Binding interface to implementation

A very powerful function of a service container is that it binds interfaces to implementations. Suppose there is an EventPusher interface and its implementation class RedisEventPusher. After writing the RedisEventPusher implementation of the interface, you can register it to the service container:

$this->app->bind(
	'App\Contracts\EventPusher',
	'App\Services\RedisEventPusher'
);

This code tells the container that when a class needs the implementation of EventPusher, it will inject RedisEventPusher. Now we can inject the dependency of EventPusher interface in the constructor or any other place where dependency is injected through the service container:

use App\Constracts\EventPusher

/**
 * Create a new class instance
 */
public function _construct(Event $pusher) {
	$this->pusher = $pusher;
} 

Context binding

Sometimes we may have two classes that use the same interface, but we want to inject different implementations into each class. For example, two controllers rely on different implementations of the Illuminate\Contracts\Filesystem\Filesystem interface. Laravel defines a simple and smooth interface for this purpose:

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\VideoController;
use App\Http\Controllers\PhotoControllers;
use Illuminate\Contracts\Filesystem\Filesystem;

$this->app->when(PhotoController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('local');
    });

$this->app->when(VideoController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('s3');
    });

label

In a few cases, we need to parse all bindings under a specific category. For example, you are building a Report aggregator that receives multiple different Report interface implementations. After registering the Report implementation, you can assign a tag to them through the tag method:

$this->app->bind('SpeedReport', function () {
    //
});

$this->app->bind('MemoryReport', function () {
    //
});

$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

After these services are labeled, they can be easily parsed through the tagged method:

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});

3 Analysis

make method

Use the make method, which takes the class name or interface name you want to resolve as a parameter:

$fooBar = $this->app->make('HelpSpot\API');

If your code location cannot access the $app variable, you can use the auxiliary function app:

$api = app('HelpSpot\API');

Automatic injection

Finally, it is also the most commonly used. You can simply parse objects from containers by type prompting dependencies in class constructors. This is how controllers, event listeners, queue tasks, middleware, etc. In practice, this is how most objects are parsed from containers.
The container will automatically inject dependency for its parsing class. For example, you can give type prompt for the warehouse defined by the application in the constructor of the controller. The warehouse will automatically parse and inject the class:

<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use App\Users\Repository as UserRepository;

class UserController extends Controller{
    /**
     * User warehouse instance
     */
    protected $users;

    /**
     * Create a controller instance
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show users by specifying ID
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        //
    }
}

4 container events

An event will be triggered every time the service container parses an object. You can use the resolving method to listen to the event:

$this->app->resolving(function ($object, $app) {
    // Called when container resolves object of any type...
});

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
    // Called when container resolves objects of type "HelpSpot\API"...
});

As you can see, the parsed object is passed to the callback function, allowing you to set additional properties for the object before it is passed to the consumer.

Tags: PHP Laravel

Posted on Wed, 10 Jun 2020 22:44:21 -0400 by datafan