How to use tagging in Laravel's service container?

3k Views Asked by At

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

2

There are 2 best solutions below

0
On

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.

0
On

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.