Reputation: 43
I need to know what is the purpose of using service container's tagging and how to use it by example this is what I have tried so far.
class MemoryReport
{
}
class SpeedReport
{
}
class ReportAggregator
{
public function __construct(MemoryReport $memory, SpeedReport $speed)
{
}
}
App::bind('MemoryReport', function () {
return new MemoryReport;
});
App::bind('SpeedReport', function () {
return new SpeedReport;
});
App::tag(['MemoryReport', 'SpeedReport'], 'reports');
App::bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
$reportAggregator = resolve('ReportAggregator');
dd($reportAggregator);
This is the error I get.
Argument 1 passed to ReportAggregator::__construct() must be an instance of MemoryReport, instance of Illuminate\Container\RewindableGenerator given, called in /media/mazzam/9068A9DC68A9C0F81/M.azzam/Learning/laravel/00 Tutorial/tut/routes/web.php on line 80
Upvotes: 4
Views: 3528
Reputation: 1029
According to the documentation, tagging is used to resolve a certain "category" of binding.
I will explain what does this means by showing you some code from one of our projects.
We use several OCR systems to scan the uploaded documents:
App\Support\OCR\GoogleVision
App\Support\OCR\AmazonTextract
App\Support\OCR\Tesseract
...All these classes implement the App\Contracts\OCR
interface:
interface OCR
{
public function scan(File $file): ScannedFile;
}
We grouped all the OCR(s) into a tag named ocrs
:
// AppServiceProvider -> register method
$this->app->tag([GoogleVision::class, AmazonTextract::class, Tesseract::class], 'ocrs');
Then we inject the ocrs
tag into the Scan
object as follows:
$this->app->bind(Scan::class, function() {
return new Scan(...$this->app->tagged('ocrs'));
});
As you might have noticed, we've used the array spread operator ...
which spreads the array elements and pass them individually to the Scan
object.
Let's see how the Scan
class looks like:
namespace App\Support;
class Scan
{
private array $ocrs;
public function __construct(App\Contracts\OCR ...$ocrs)
{
$this->ocrs = $ocrs;
}
public function scan(File $file)
{
foreach ($this->ocrs as $ocr)
{
$scannedFile = $ocr->scan($file);
// ...
}
}
}
The Scan's contractor uses the argument unpacking (variadic) which means that we can pass N number of objects that implementing the App\Contracts\OCR
.
You may be wondering, why didn't use type-hinting instead of tagging?
That's because we constantly add/remove OCR systems based on the customer's needs.
So, by using the tags, we are not tied to specific implementations, since we can easily add/remove the classes based on the customer's needs.
I hope, I answered your question.
Upvotes: 6
Reputation: 6544
Tagging allows you to group services under a common name. This is for example useful if you have multiple services implementing the same interface and you need one of the interfaces method to be executed for each of the implementations:
interface Messenger
{
public function sendMessage(string $recipient, string $message): void;
}
class SlackMessenger implements Messenger
{
public function sendMessage(string $recipient, string $message): void
{
app(Slack::class)->send($recipient, $message);
}
}
class TwilioMessenger implements Messenger
{
public function sendMessage(string $recipient, string $message): void
{
app(Twilio::class)->sendSMS($recipient, $message);
}
}
// AppServiceProvider::register()
App::tag([SlackMessenger::class, TwilioMessenger::class], Messenger::class);
// somewhere in your application
$messengers = app()->tagged(Messenger::class);
foreach ($messengers as $messenger) {
$messenger->sendMessage($recipient, $message);
}
Note: This is a fictional test case and the underlying services may be different. You also need to add namespaces and use
imports.
In your case, you don't need to bind any of the classes. If their construction is based on other services of the service container, type-hinting is sufficient.
Upvotes: 5