I'm developing an application in which I have some handlers as services I want to be able to invoke. Both of them implements an ItemHandlerInterface
.
I would like to be able in a controller to retrieve all the ItemHandlerInterface
services collection, without wiring them manually.
So far I tagged them specifically:
services.yaml
_instanceof:
App\Model\ItemHandlerInterface:
tags: [!php/const App\DependencyInjection\ItemHandlersCompilerPass::ITEM_HANDLER_TAG]
lazy: true
And try to retrieve my service collection in a controller. It works if only one service implements ItemHandlerInterface
, but as soon as I create several ones (like below TestHandler
and Test2Handler
, I end up with a The service "service_locator.03wqafw.App\Controller\ItemUpdateController" has a dependency on a non-existent service "App\Model\ItemHandlerInterface".
How can I retrieve dynamically all services implementing my interface?
One dirty solution would be to force all ItemHandlerInterface
with public: true
and pass Container
to my controller constructor. But this is ugly and I would like to find a more elegant way.
ItemUpdateController
namespace App\Controller;
use App\Model\ItemHandlerInterface;
use App\Service\ItemFinder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use App\Model\Item;
use Psr\Container\ContainerInterface;
/**
* Class ItemUpdateController
*
* @package App\Controller
*/
class ItemUpdateController extends AbstractController
{
/**
* @var ContainerInterface
*/
protected $locator;
public function __construct(ContainerInterface $locator)
{
$this->locator = $locator;
}
public static function getSubscribedServices()
{
// Try to subscribe to all ItemHandlerInterface services
return array_merge(
parent::getSubscribedServices(),
['item_handler' => ItemHandlerInterface::class]
);
}
/**
* @param string $id
* @param RequestStack $requestStack
* @param ItemFinder $itemFinder
*
* @return Item
* @throws \Symfony\Component\Debug\Exception\ClassNotFoundException
*/
public function __invoke(
string $id,
RequestStack $requestStack,
ItemFinder $itemFinder
) {
// Find item
$item = $itemFinder->findById($id);
// Extract and create handler instance
$handlerName = $item->getHandlerName();
if($this->locator->has($handlerName)) {
$handler = $this->locator->get($handlerName);
$request = $requestStack->getCurrentRequest();
$payload = json_decode($request->getContent());
call_user_func($handler, $payload, $request);
return $item;
}
}
}
src/ItemHandler/TestHandler.php
namespace App\ItemHandler;
use App\Model\ItemHandlerInterface;
use Doctrine\ORM\EntityManagerInterface;
class TestHandler implements ItemHandlerInterface
{
// implementation
}
src/ItemHandler/Test2Handler.php
namespace App\ItemHandler;
use App\Model\ItemHandlerInterface;
use Doctrine\ORM\EntityManagerInterface;
class Test2Handler implements ItemHandlerInterface
{
// implementation
}