2

I'm starting a new slim 4 application, and current slim versions are more just a router than a framework and do not have di container out of the box, so I choose PHP-DI as a DI container. And to not describe each controller in the container and to not pass a container instance to the controllers I'm using PHP-DI autowiring.

But with such an approach I need to inject all dependencies to each controller, and most of those dependencies are reusable by all controllers. Example:

public function __construct(
    Environment $twig,
    Messages $flashMessage,
    RouteCollectorInterface $routeCollector
)
{
    $this->render = $twig;
    $this->flashMessage = $flashMessage;
    $this->routeCollector = $routeCollector;
}

Is there a way to make a base controller, inject all reusable dependencies to it so when DIc create/autowire each new controller, all dependencies already injected b-z controller extend base one? Also, base controller probably should use method injection, otherwise, I still need to create a constructor and pass all injection in each controller to the base controller like this:

public function __construct(
    Environment $twig,
    CommandBus $commandBus,
    Messages $flashMessage,
    RouteCollectorInterface $routeCollector
)
{
     parent::__construct($twig, $flashMessage, $routeCollector);
    $this->commandBus = $commandBus;
}

which is probably the same that inject dependencies to each controller without base controlelr at all.

I tried to configure base contoller:

   $definitions[BaseController::class] = static function(ContainerInterface $container): BaseController {
        /** @var BaseController $controller */
        $controller =new BaseController();
        $controller->setView($container->get(Environment::class));
        $controller->setRouter($container->get(RouteCollectorInterface::class));
        $controller->setFleshMessage($container->get(Messages::class));

        return $controller;
    };

but it's not working, and dependencies are null for each controller which extending base controller.

PHP-DI http://php-di.org/doc/best-practices.html#writing-controllers propose to use property injection, but it's still looks like a boilerplate for me to inject all reusable injections for each controller and it's breaking encapsulation when we inject into private properties.

One of the solutions I can think is to make some kind of ControllerHelper with all reusable dependencies as a bridge/wrapper and inject it instead of all those dependencies, it 'll reduce amount of duplicated code. But still, maybe there is a better solution???

Updated: I know that it's actually how it works in OOP, if you extend base class you need to pass all the dependencies to it. But to reduce some duplication, some magic would be good here :) also it's not an external package that will be used by someone else so probably such kind of magic is okay if it's possible to achieve somehow at all.

Updated: in other words what I'm trying to achieve is:

 class SomeController extends BaseController {
// in this case constructor is empty 

        public function index(RequestInterface $request, ResponseInterface $response) {
             return $this->render($response, $templateName, [somedata]);
        }
    }

where $this-render is a BaseController class method which using template engine dependency to render view:

public function render(ResponseInterface $request, string $templateName, array $data): ResponseInterface {
          $response->getBody()->write(
            $this->render->render(
                $templateName,
                $data
            )
        );

        return $response;
}

as you can see I do not want to inject dependencies to the SomeController but somehow inject them once to the BaseController

Bogdan Dubyk
  • 4,756
  • 7
  • 30
  • 67

1 Answers1

0

You could try doing it like the following "BaseController" controller class:

<?php

namespace App\Application;

use Twig\Environment;
use Laminas\Diactoros\Response;
use Psr\Http\Message\ResponseInterface;

abstract class BaseController
{
    private $template;

    public function __construct(Environment $template)
    {
        $this->template = $template;
    }

    public function view(string $template, array $data = []) : ResponseInterface
    {
        $result = $this->template->render($template, $data);

        $response = new Response();
        $response->getBody()->write($result);
        return $response;
    }
}

Then when calling your controller you could do this:

<?php

namespace App\Controller\Auth;

use App\Application\Controller;

class AuthController extends BaseController
{

    public function showLogin()
    {
        return $this->view('auth/login.html');
    }
}
Franck Mercado
  • 169
  • 1
  • 5