23 Feb 2021

Laravel Tagging Explained

According to Laravel’s documentation, tagging used to resolve a particular category of binding.

For example, you could group CpuReport, MemoryReport, and DiskReport under a tag named reports.

But that’s still vague for many developers.

In this post, I will explain the tagging by looking at a real-world example, so let’s get started.

Scanning a file using different OCRs

In one of my side projects, I used the following OCR systems to scan the uploaded document:

  • Google Vision.
  • Amazon Textract.
  • Tesseract.

Using multiple OCRs increases the scanning accuracy; therefore, it gives the user the best possible results.

After a bit of thinking, I realized that the OCRs list is subject to change; for example, I might need to remove Tesseract and replace it with something else, such as Abbyy. Maybe I need to add both Abbyy and MicrosoftComputerVision.

If I explicitly type-hint the dependencies, then my Recognizer class would look like this:

class Recognizer
{
    public function __construct(
        private GoogleVision $googleVision,
        private AmazonTextract $amazonTextract,
        private Tesseract $tesseract
    )
    {
    }
}

And let’s say that I need to add two more OCRs, Abbyy and MicrosoftComputerVision, then I have no choice but modifying the __constructor :

class Recognizer
{
    public function __construct(
        private GoogleVision $googleVision,
        private AmazonTextract $amazonTextract,
        private Tesseract $tesseract,
        
        // New OCRs
        private MicrosoftVision $microsoftVision,
        private Abbyy $abbyy,
    )
    {
    }

    public function recognize(File $file)
    {
        // recognize
    }
}

What if I need to get rid of Tesseract? Again, I have to open the Recognizer class and remove it from the __constructor.

Fortunately, the OCR classes implement the App\Contracts\OCR interface:

interface OCR
{
    public function recognize(File $file): RecognizedFile;
}

This means that the recognize method is available for all the OCR classes:

class Recognizer
{
    public function __construct(
        private GoogleVision $googleVision,
        private AmazonTextract $amazonTextract,
        private Tesseract $tesseract,
        private MicrosoftVision $microsoftVision,
        private Abbyy $abbyy,
    )
    {
    }

    public function recognize(File $file)
    {
        $this->googleVision->recognize($file);
        $this->amazonTextract->recognize($file);
        $this->tesseract->recognize($file);
        $this->microsoftVision->recognize($file);
        $this->abbyy->recognize($file);
    }
}

Tagging the OCR classes

Instead of using dependency injection by type-hinting the OCR classes and then calling the `recognize method on each one of them, I can easily create a group that contains the supported OCR as follows:

class AppServiceProvider
{
    public function register()
    {
        $this->app->tag([
            GoogleVision::class, 
            AmazonTextract::class, 
            Tesseract::class
        ], 'ocrs');
    }
}

This grouping is called tagging.

Then I will inject the tagged classes into App\Support\Recognizer as follows:

// AppServiceProvider
// register() method
$this->app->bind(Recognizer::class, function() {
    return new Recognizer(...$this->app->tagged('ocrs'));
});

Since the $this->app->tagged returns an Iterator. I can use the array spread operator to inject all the tagged dependencies.

The ... operator spreads the array elements and pass them individually to the Recognizer object.

Let’s modify the Recoginzer class to have the new changes:

class Recognizer
{
    public function __construct(App\Contracts\OCR ...$ocrs)
    {
    }

    public function recognize(File $file)
    {
        foreach ($this->ocrs as $ocr) {
            $ocr->recognize($file);
        }
    }
}

As you can see, the class became more maintainable than the previous implementation, if we got a new OCR then all we need to do is to add into the ocrs tag, that’s it.

I hope you enjoyed reading this post; keep an eye on the upcoming posts.