phpslimphp-dislim-4

Overriding injected class from routing group in Slim 4?


I have a Slim4 Application composed of several modules separated in different routing groups, like so:

$app->group('/app', function(RouteCollectorProxy  $app) {
   /*blah blah*/
})->add(MyMiddleWare::class);

$app->group('/api', function(RouteCollectorProxy  $app) {
   /*blah blah*/
})->add(MyMiddleware::class);

$app->group('/admin', function(RouteCollectorProxy  $app) {
   /*blah blah*/
})->add(MyMiddleware::class);

MyMiddleware receives an Interface

class MyMiddleware
{
    public function __construct(IMyInterface $myServiceImplementingInterface) { /*blah blah*/ }
}

When we setup the container, we tell it which class to inject so PHP-DI know which class to construct the middleware with:

/* bootstraping */
$containerBuilder = new ContainerBuilder();
$containerBuilder->addDefinitions(__DIR__ . '/container.php');
$container = $containerBuilder->build();

and

/*container.php*/
return [
    IMyInterface::class => function (ContainerInterface $container) {
        return new MyServiceImplementingInterface();
    },
];

My main question is:

Would it be possible to somehow override the implementation of the container setup for IMyInterface::class based on the Routing Group ? so I could have something like:

Main container setup:

/*container.php*/
return [
    IMyInterface::class => function (ContainerInterface $container) {
        return new MyServiceImplementingInterface();
    },
];

Specific route group container setup:

/*container.admin.php*/
return [
    IMyInterface::class => function (ContainerInterface $container) {
        return new AnotherServiceImplementingInterface();
    },
];

Solution

  • I suggest using two different objects of MyMiddleware class for different groups, each constructed using appropriate implementation of IMyInterface. You can tell PHP-DI to call the constructor with the parameters you want.

    Here I created two instances of MyMiddleware, one with the name AdminMiddleware and the other named ApiMiddleware in the container. using DI\create()->constructor() method, I configure the DI to inject different implementations of IMyInterface while building these two objects:

    <?php
    
    use DI\ContainerBuilder;
    use Slim\Factory\AppFactory;
    
    // this is the path of autoload.php relative to my index.php file
    // change it according to your directory structure
    require __DIR__ . '/../vendor/autoload.php';
    
    interface IMyInterface {
        public function sampleMethod();
    }
    
    class MyServiceImplementingInterface implements IMyInterface {
        public function sampleMethod() {
            return 'This implementation is supposed to be used for API endpoint middleware';
        }
    }
    
    class AnotherServiceImplementingInterface implements IMyInterface {
        public function sampleMethod() {
            return 'This implementation is supposed to be used for Admin middleware';
        }
    }
    
    class MyMiddleware
    {
        private $service;
        public function __construct(IMyInterface $myServiceImplementingInterface) { 
            $this->service = $myServiceImplementingInterface;
        }
        public function __invoke($request, $handler)
        {
            $response = $handler->handle($request);
            $response->getBody()->write($this->service->sampleMethod());
            return $response;
        }
    }
    
    $containerBuilder = new ContainerBuilder();
    $containerBuilder->addDefinitions([
        'AdminMiddleware' => DI\create(MyMiddleware::class)->constructor(DI\get(AnotherServiceImplementingInterface::class)),
        'ApiMiddleware' => DI\create(MyMiddleware::class)->constructor(DI\get(MyServiceImplementingInterface::class))
    ]);
    
    $container = $containerBuilder->build();
    
    AppFactory::setContainer($container);
    $app = AppFactory::create();
    
    $app->group('/admin', function($app) {
        $app->get('/dashboard', function($request, $response, $args){
            return $response;
        });
    })->add($container->get('AdminMiddleware'));
    $app->group('/api', function($app) {
        $app->get('/endpoint', function($request, $response, $args){
            return $response;
        });
    })->add($container->get('ApiMiddleware'));
    
    $app->run();