3
votes

I have the ff class:

namespace App\Component\Notification\RealTimeNotification;

use App\Component\Notification\NotificationInterface;

class EmailNotification implements NotificationInterface
{   
    private $logNotification;
    private $mailer;
    private $engine;

    // This will appear on From field on Email.
    private $mailerFrom;

    public function __construct(LogNotification $logNotification, \Swift_Mailer $mailer, \Twig_Environment $twig, string $from)
    {
        $this->logNotification = $logNotification;
        $this->mailer = $mailer;
        $this->twig = $twig;

        $this->mailerFrom = $mailerFrom;
    }

    public function send(array $options): void
    {   
        // Resolve options
        $this->resolveOptions($options);

        $sendTo = $options['sendTo'];
        $subject = $options['subject'];

        $template = $options['template'];
        $data = $options['data'];
        $body = $this->createTemplate($template, $data);

        $this->sendEmail($sendTo, $subject, $body);
    }

    protected function sendEmail($sendTo, $subject, $body): void
    {
        dump($this->mailerFrom);
        $message = (new \Swift_Message())
            ->setSubject($subject)
            ->setFrom($this->mailerFrom)
            ->setTo($sendTo)
            ->setBody($body, 'text/html')
        ;

        $this->mailer->send($message);
    }

    protected function createTemplate($template, $data): string
    {
        return $this->twig->render($template, $data);
    }

    protected function resolveOptions(array $options): void
    {

    }

    protected function createLog(array $email): void
    {
        $message = 'Email has been sent to: ' . $email;

        $this->logNotification->send([
            'message' => $message,
        ]);
    }
}

I tried to manually wire all the arguments with the following:

# Notification
app.log_notification:
    class: App\Component\Notification\RealTimeNotification\LogNotification

app.email_notification:
    class: App\Component\Notification\RealTimeNotification\EmailNotification
    decorates: app.log_notification
    decoration_inner_name: app.log_notification.inner
    arguments:
        $logNotification: '@app.log_notification.inner'
        $mailer: '@mailer'
        $twig: '@twig'
        $from: '%mailer_from%'

However, when I run the app it throws the exception:

Cannot autowire service "App\Component\Notification\RealTimeNotification\EmailNotification": argument "$from" of method "__construct()" must have a type-hint or be given a value explicitly

Why is this event happening?

Thanks!

2
It's because you are trying to inject your EmailNotification into yet another service using a typehint against EmailNotification. So autowire is looking in the container for a service id matching the class name. It won't automatically use app.email_notification. So it tries to make a new service and fails on the scalar. If you are using autowire then you no longer need service ids such as app.email_notification. Just use the class name. And if you really want to keep you id then add an alias. It's all documented. Also a bunch of questions on the exact same topic. - Cerad
@Cerad, you are actually right. That's the way i did it. Thanks! - iamjc015

2 Answers

2
votes

Answer by @Matteo is great! You can even drop service definition and delegate to parameter binding since Smyfony 3.4+/2018+:

 # config/services.yaml
 services:
     _defaults:
         bind:
             $adminEmail: '[email protected]'

     # same as before
     App\:
         resource: '../src/*'

Do you want more example and logic behind it? Find it here: https://www.tomasvotruba.cz/blog/2018/01/22/how-to-get-parameter-in-symfony-controller-the-clean-way/#change-the-config

0
votes

Autowiring only works when your argument is an object. But if you have a scalar argument (e.g. a string), this cannot be autowired: Symfony will throw a clear exception.

You should Manually Wiring Arguments and explicitly configure the service, as example:

# config/services.yaml
services:
    # ...

    # same as before
    App\:
        resource: '../src/*'
        exclude: '../src/{Entity,Migrations,Tests}'

    # explicitly configure the service
    App\Updates\SiteUpdateManager:
        arguments:
            $adminEmail: '[email protected]'

Thanks to this, the container will pass [email protected] to the $adminEmail argument of __construct when creating the SiteUpdateManager service. The other arguments will still be autowired.

Hope this help