0

In my Slim v4, application, I am setting up the container definitions as below.

$containerBuilder = new ContainerBuilder();
$containerBuilder->useAttributes(true);

$containerBuilder->addDefinitions([
    MailerInterface::class => function (ContainerInterface $container) {
        $dsn = sprintf(
            '%s://%s:%s@%s:%s',
            "smtp",
            "username",
            "password",
            "hostname",
            "587"
        );

        return new Mailer(Transport::fromDsn($dsn));
    }
]);

I can use the MailerInterface in my controllers without any issue.

<?php
abstract class BaseController
{
    protected MailerInterface $mailer;

    /* DI works in constructor as part of the controller */
    public function __construct(MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }
}

This works perfectly fine as part of the controller. But I am looking for creating a service which can be used anywhere in the application and not only in the controllers.

My intended usage of the service class is as follows. I don't want my users to deal with dependencies creation and hence the empty constructor.

$m = new MailerService();
$m->sendEmail(($email));

My service class is as below as I am trying to inject the dependency as per doc in https://php-di.org/doc/attributes.html.

final class MailerService
{
    private MailerInterface $mailer;

    public function __construct(#[Inject('MailerInterface')] MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }

    public function sendEmail(Email $email): void
    {
        $this->mailer->send($email);
    }
}

What is missing in my implementation? Any help is appreciated.

Purus
  • 5,701
  • 9
  • 50
  • 89
  • It seems like you're missing the part where you build the container and where you pass the container to the Slim App instance. Second, when you declare a class or interface, you don't need to add additional attributes. – odan Feb 12 '23 at 16:55
  • @odan I already have a successful slim4 app working based on container. My current requirement is to create utility classes for my application, which takes care of dependencies internally. – Purus Feb 12 '23 at 17:41
  • Why should a "utility" class handle the dependencies when you have a DI container that handles this already perfectly? – odan Feb 12 '23 at 18:04
  • My intension is to create utility classes. This is for a new open-source project where other developers will use the "utility" class directly without having to deal with creating controllers and other DI related stuffs. – Purus Feb 12 '23 at 18:21
  • BTW, @odan all your articles and blogs are Slim is super useful. Thanks a ton for that. – Purus Feb 12 '23 at 18:22

1 Answers1

0

Dependency injection containers don't change how normal PHP code works; if you write new MailerService(); that still means "run the constructor with no arguments".

What dependency injection containers do is define how to create an instance when you ask for one from the container. (Generally, they also return the same instance each time you ask for one, rather than repeating the constructor call.)

In other words, when you write this:

$m = $container->get(MailerService::class);

Then the container runs this:

$dsn = sprintf(
    '%s://%s:%s@%s:%s',
    "smtp",
    "username",
    "password",
    "hostname",
    "587"
);
$mailer = new Mailer(Transport::fromDsn($dsn));
$m = new MailerService($mailer);

So what you need to do is replace code using the new operator. That means either:

  • Make the container itself available, and ask for the instance explicitly as I did in that example. This generally makes dependencies harder to keep track of, but can be useful in a controller which is tightly coupled to the framework already, and uses a lot of different services. A common rule of thumb is that your code should have an "application layer" which doesn't directly access the container.
  • Make your MailerService a constructor parameter in all the controllers and other services that need it. Repeat for all your services. Then the framework will ask for the controller, and the container will work out what objects need to be created, in what order, to fill all its parameters.
IMSoP
  • 89,526
  • 13
  • 117
  • 169
  • Also note that the DI container instance should not be used directly in your application layer. The "layer" around your core application should handle this automatically. – odan Feb 13 '23 at 12:47
  • @odan Yes, I tried to imply that in my first bullet point; I've added an explicit mention of the concept of an "application layer". Whether the controllers themselves should be aware of the container is more of a grey line, IMO - if only one action needs a particular service, it's wasteful initialising it every time the constructor is created, but if your controllers get too busy, you might lose track of the dependencies if they're being pulled out of the container whenever needed. – IMSoP Feb 13 '23 at 13:29