Home » Php » How to inject multiple classes that share the same interface in Laravel 4

How to inject multiple classes that share the same interface in Laravel 4

Posted by: admin October 29, 2017 Leave a comment

Questions:

Say I have an interface CrawlerInterface with implementation PageCrawler and FeedCrawler; if we happen to need both classes in a controller, how can that be achieved with constructor injection?

Previously we use a central ServiceProvider to register (i.e. App::bind) such classes, but in most cases we only have 1 implementation of an interface, so said problem hasn’t occured to us yet.

PS: I also wonder if this problem suggests we should split the controller.


Updates:

Thanks for the comments and response, to explain, said interface has only one public method: crawl($uri), and both page/feed crawler implements it as given a resource identifier, return resource.


My follow up question:

Say we are in a calculator scenario where Addition, Subtraction and Multiplication share the same interface Operation, which has only 1 public method run, at some point we will still encounter this problem right? How do we handle situation like these in general with ServiceProvider?

Answers:

If each crawler exists for a different reason, you can use arbitrary names for your instances, for example:

App::bind('crawler.allArticles', 'PageCrawler');
App::bind('crawler.latestArticles', 'FeedCrawler');

For the controller:

App::bind('CrawlerController', function($app) {
    return new CrawlerController(
        App::make('crawler.allArticles'),
        App::make('crawler.latestArticles')
    );
});

Your controller code would then use each crawler differently:

public function showLatestArticlesAction()
    $latestArticles = $this->latestArticlesCrawler->crawl();
    // ...
}

public function showAllArticlesAction()
    $allArticles = $this->allArticlesCrawler->crawl();
    // ...
}

If you just have a list of crawlers where each is used for the same thing, you probably want to do something like:

App::bind('crawlers', function($app) {
    return [
        App::make('PageCrawler'),
        App::make('FeedCrawler'),
    ];
});

In your controller, you’ll get a list of “crawlers” by configuring it like so:

App::bind('CrawlerController', function($app) {
    return new CrawlerController(App::make('crawlers'));
});

Your controller code could be something like this:

public function showArticlesAction()
    $allArticles = array();
    foreach ($this->crawlers as $crawler) {
        $allArticles = array_merge($allArticles, $this->crawler->crawl());
    }
    // ...
}

Questions:
Answers:

Ok lets assume you have a CrawlerController

class CrawlerController extends BaseController 
{
    protected $crawler1;
    protected $crawler2;

    public function __construct(CrawlerInterface $c1, CrawlerInterface $c2)
    {
        $this->crawler1 = $c1;
        $this->crawler2 = $c2;
    }
}

an interface

interface CrawlerInterface{}

and concrete implementations of that intefrace called PageCrawler and FeedCrawler

class PageCrawler implements CrawlerInterface{}
class FeedCrawler implements CrawlerInterface{}

You would inject the dependencies by writing a service locator like

App::bind('CrawlerController', function($app) {
    $controller = new CrawlerController(
        new PageCrawler,
        new FeedCrawler
    );
    return $controller;
});

But as suggested by others you should rethink your logic, use it only if this kind
of architecture is unavoidable

Questions:
Answers:

I think that the interface won’t help you in this case.

By doing:

App::bind('CrawlerInterface', '<implementation>');

You need to choose one:

App::bind('CrawlerInterface', 'PageCrawler');

or

App::bind('CrawlerInterface', 'FeedCrawler');

And then Laravel will inject it:

class CrawlerController {

    public function __construct(CrawlerInterface $crawler)
    {
    }

}

To have both you have 2 options

-Have 2 different interfaces

-Inject the implementations directly:

class CrawlerController {

    public function __construct(PageCrawler $pageCrawler, FeedCrawler $feedCrawler)
    {
    }

}

But I also think that, if you need something like this, you better rethink your logic.