[Laravel5.2 documentation] architecture-service container
1. Introduction
Laravel service container is a powerful tool for managing dependencies and executing dependency injection. Dependency Injection sounds fancy. its essence is to inject class dependencies into classes through constructors or in some cases through the set method.
Let's look at a simple example:
Mailer = $ mailer;}/*** buy a podcast ** @ return void */public function handle (){//}}
In this example, when a podcast is purchased, the PurchasePodcast task needs to send emails. Therefore, you need to inject a service that can send emails. Because the service is injected, we can easily use another implementation to replace it. during the test, we can also "simulate" or create a fake mail implementation.
A deep understanding of the Laravel service container is crucial for building powerful large-scale Laravel applications. it is also helpful for contributing code to the Laravel core.
2. bind
Almost all service container bindings are completed in the service provider. Therefore, the containers used in the demo in this chapter are in this context. if a class is not based on any interface, it is not necessary to bind it to the container. The container does not need to be notified about how to build an object because it uses PHP's reflection service to automatically parse a specific object.
In a service provider, you can use the $ this-> app variable to access the container, and then use the bind method to register a binding. this method requires two parameters, the first parameter is the class name or interface name we want to register, and the second parameter is the closure of the returned class instance:
$this->app->bind('HelpSpot\API', function ($app) { return new HelpSpot\API($app['HttpClient']);});
Note that we accept the container itself as a parameter of the parser, and then we can use this container to parse the sub-dependencies of the object we are building.
Bind a Singleton
The singleton method binds a class or interface that only needs to be parsed once to the container. then, the call to the container will return the same instance:
$this->app->singleton('FooBar', function ($app) { return new FooBar($app['SomethingElse']);});
Bind instance
You can also bind an existing object instance to the container using the instance method, and then call the container will always return the given instance:
$fooBar = new FooBar(new SomethingElse);$this->app->instance('FooBar', $fooBar);
Bind interface to implementation
A very powerful feature of a service container is its ability to bind interfaces to implementations. Assume that there is an EventPusher interface and its RedisEventPusher implementation. after writing the RedisEventPusher implementation of this interface, you can register it to the service container:
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');
This code tells the container that RedisEventPusher will be injected when an EventPusher implementation is required for a class. now we can type the EventPusher interface in the constructor or any other places where dependencies are injected through the service container:
Use App \ Contracts \ EventPusher;/*** create a new class instance ** @ param EventPusher $ pusher * @ return void */public function _ construct (EventPusher $ pusher) {$ this-> pusher = $ pusher ;}
Context binding
Sometimes we may have two classes using the same interface, but we want to inject different implementations into each class. for example, when the system receives a new order, we want to send an event through PubNub instead of Pusher. Laravel defines a simple and smooth way to define this behavior:
$this->app->when('App\Handlers\Commands\CreateOrderHandler') ->needs('App\Contracts\EventPusher') ->give('App\Services\PubNubEventPusher');
You can even pass a closure to the give method:
$this->app->when('App\Handlers\Commands\CreateOrderHandler') ->needs('App\Contracts\EventPusher') ->give(function () { // Resolve dependency... });
Bind original value
Sometimes you may have a class that gets several injection classes, but you also need an original injection value, such as integer data, you can easily use context binding to inject any value required by the specified class:
$this->app->when('App\Handlers\Commands\CreateOrderHandler') ->needs('$maxOrderCount') ->give(10);
Tag
In a few cases, we need to parse all bindings under a specific category. for example, you may be building a Report aggregator that receives multiple different Report interfaces. after registering the Report implementation, you can use the tag method to assign a tag to them:
$this->app->bind('SpeedReport', function () { //});$this->app->bind('MemoryReport', function () { //});$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
After these services are tagged, you can easily parse them using the tagged method:
$this->app->bind('ReportAggregator', function ($app) { return new ReportAggregator($app->tagged('reports'));});
3. Analysis
There are many ways to parse objects from the container. First, you can use the make method, which receives the class name or interface name you want to parse as the parameter:
$fooBar = $this->app->make('FooBar');
Second, you can access the container as an array because it implements the ArrayAccess interface of PHP:
$fooBar = $this->app['FooBar'];
Finally, it is also the most common. you can simply parse objects from the container by prompting the type of dependencies in the class constructor, this method includes controllers, event listeners, queue tasks, and middleware. In practice, most objects are parsed from the container.
The container automatically injects dependencies into the parsing class. for example, you can type the Repository defined for the application in the controller constructor. the repository automatically parses and injects the class:
Users = $ users;}/*** use the specified ID to display the user ** @ param int $ id * @ return Response */public function show ($ id ){//}}
4. container events
The service container triggers an event every time an object is parsed. you can use the resolving method to listen to this event:
$ This-> app-> resolving (function ($ object, $ app) {// called when the container parses all types of objects }); $ this-> app-> resolving (function (FooBar $ fooBar, $ app) {// called when the container parses the "FooBar" object });
As you can see, the parsed object will be passed to the callback, allowing you to set additional attributes for the object before it is passed to the consumer.