This article was published over 2 years ago. Some information may be outdated.
According to Laravel's documentation, tagging is used to resolve a particular category of binding.
For example, you could group CpuReport, MemoryReport, and DiskReport under a tag named reports.
But that explanation is still vague for many developers.
This post explains tagging through a real-world example.
Scanning a file using different OCRs
In one of my side projects, I used the following OCR systems to scan uploaded documents:
- Google Vision.
- Amazon Textract.
- Tesseract.
Using multiple OCRs increases the scanning accuracy, giving the user the best possible results.
After thinking about it, I realized that the OCR list is subject to change. I need to remove Tesseract and replace it with something else, such as Abbyy. Or I need to add both Abbyy and MicrosoftComputerVision.
If you explicitly type-hint the dependencies, the Recognizer class looks like this:
class Recognizer
{
public function __construct(
private GoogleVision $googleVision,
private AmazonTextract $amazonTextract,
private Tesseract $tesseract
)
{
}
}
And if you need to add two more OCRs, Abbyy and MicrosoftComputerVision, you have no choice but to modify 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 you need to get rid of Tesseract? Again, you have to open the Recognizer class and remove it from the __constructor.
All 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 type-hinting every OCR class and calling the recognize method on each one individually, you can create a group that contains the supported OCRs:
class AppServiceProvider
{
public function register()
{
$this->app->tag([
GoogleVision::class,
AmazonTextract::class,
Tesseract::class
], 'ocrs');
}
}
This grouping is called tagging.
Then inject the tagged classes into App\Support\Recognizer:
// AppServiceProvider
// register() method
$this->app->bind(Recognizer::class, function() {
return new Recognizer(...$this->app->tagged('ocrs'));
});
Since $this->app->tagged returns an Iterator, you can use the array spread operator ... to inject all the tagged dependencies.
The ... operator spreads the array elements and passes them individually to the Recognizer object.
Now modify the Recognizer class to accept the new structure:
class Recognizer
{
public function __construct(App\Contracts\OCR ...$ocrs)
{
}
public function recognize(File $file)
{
foreach ($this->ocrs as $ocr) {
$ocr->recognize($file);
}
}
}
The class is now far more maintainable than the previous implementation. If you add a new OCR, all you need to do is add it to the ocrs tag.
Summary
- Tagging groups related bindings under a single label -- instead of type-hinting every dependency individually, you register them all under one tag.
- The spread operator injects tagged services --
...$this->app->tagged('ocrs')passes all tagged implementations into the constructor as variadic arguments. - Adding or removing implementations requires no constructor changes -- you only modify the tag registration in the service provider, keeping the consuming class untouched.