I can’t get my Mailer working when creating a service.
I’ve been following a few tutorials.
I’ve been trying to inject my dependencies but there is no way to get my $this->container->render() working
I’m getting the following error message
ServiceNotFoundException:
The service "App\Services\Mailer" has a dependency on a non-existent service "templating".
What would be a good way to inject the templating service in my Mailer service? I know this is called dependency injection but I can’t have it work properly.
I tried to follow this but no luck: RenderView in My service
My Controller:
use App\Services\Mailer;
class ScriptController extends AbstractController
{
private function getThatNotifSent($timeframe, Mailer $mailer)
{
// Some code
foreach ( $users as $user ) {
$mailer->sendNotification($user, $cryptos, $news, $timeframe);
$count++;
}
$response = new JsonResponse(array('Mails Sent' => $count));
return $response;
}
}
My service:
<?php
// src/Service/Mailer.php
namespace App\Services;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Mailer
{
private $mailer;
private $templating;
public function __construct(\Swift_Mailer $mailer ,ContainerInterface $templating)
{
$this->mailer = $mailer;
$this->templating = $templating;
}
public function sendNotification($user, $cryptos, $news, $timeframe)
{
$message = (new \Swift_Message('Your Daily Digest Is Here!'))
->setFrom('no-reply#gmail.com')
->setTo($user->getEmail())
->setBody(
$this->templating->render(
'emails/notification.html.twig',
array(
'timeframe' => $timeframe,
'cryptos' => $cryptos,
'user' => $user,
'news' => $news,
)
),
'text/html'
)
->setCharset('utf-8');
$this->mailer->send($message);
return true;
}
}
My service.yaml
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
images_directory: '%kernel.project_dir%/public/images/blog'
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\EventListener\LoginListener:
tags:
- { name: 'kernel.event_listener', event: 'security.interactive_login' }
App\Services\Mailer:
arguments: ["#mailer", "#templating”]
UPDATE:
I have updated my code to follow Taylan answer. My Mailer service now looks like the following (no change to sendNotification Made)
<?php
// src/Service/Mailer.php
namespace App\Services;
use Symfony\Bundle\TwigBundle\TwigEngine;
class Mailer
{
private $mailer;
private $templating;
public function __construct(\Swift_Mailer $mailer ,TwigEngine $templating)
{
$this->mailer = $mailer;
$this->templating = $templating;
}
I still had the same error message. But after doing research online, I’ve updated my framework.yaml to the following after reading on this helpful link:
https://github.com/whiteoctober/BreadcrumbsBundle/issues/85
framework:
templating: { engines: [twig] }
It worked
Thanks for your help.
ContainerInterface typehint gives you the container, yet you named it $templating. You're supposed to get templating from container like this $this->templating = $container->get('templating').
But do you really need the container in the first place? You should be able to inject templating directly by typehinting like this Symfony\Bundle\TwigBundle\TwigEngine $templating instead of Symfony\Component\DependencyInjection\ContainerInterface $container.
P.S: You can search for services via php bin/console debug:container command.
Related
I have this deprecation message:
Since symfony/dependency-injection 5.1: The
"Symfony\Component\DependencyInjection\ContainerInterface" autowiring
alias is deprecated. Define it explicitly in your app if you want to
keep using it.
From threads such as this Symfony: Explicit define Container in Service I understand that the long-term solution is to stop using the ContainerInterface all together in my services.
My services.yaml looks like this:
parameters:
#locale: en
basepath: '%env(basepath)%'
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
globalHelper:
class: App\Service\globalHelper
public: false
The service in question (globalHelper) looks like this:
<?php
namespace App\Service;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Doctrine\ORM\EntityManagerInterface as EntityManager;
class globalHelper {
private $container;
private $em;
public function __construct(Container $container, EntityManager $em) {
$this->container = $container;
$this->em = $em;
}
I only user the container to fetch session variables like this
$this->container->get('session')->getFlashBag()->add($type, $message);
And to get the current user (security context) like this
$this->container->get('security.context')->getToken()->getUser();
Can I get these sub-components of the container separately instead? What component then would I inject to access these two parts (session and user) respectively?
--------------- Addition --------------
According to Alexis' suggestion below I modified the head of the file with
<?php
namespace App\Service;
//use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Security;
use Doctrine\ORM\EntityManagerInterface as EntityManager;
class globalHelper {
//private $container;
private $requestStack;
private $security;
private $em;
//public function __construct(Container $container, RequestStack $requestStack, Security $security, EntityManager $em) {
public function __construct(RequestStack $requestStack, Security $security, EntityManager $em) {
//$this->container = $container;
$this->requestStack = $requestStack;
$this->security = $security;
$this->em = $em;
}
then replaced
$this->container->get('session')->getFlashBag()->add($type, $message);
with
$this->requestStack->getSession()->getFlashBag()->add($type, $message);
and get this error:
Attempted to call an undefined method named "getSession" of class
"Symfony\Component\HttpFoundation\RequestStack".
if I instead to this:
$this->requestStack->get('session')->getFlashBag()->add($type, $message);
Attempted to call an undefined method named "get" of class
"Symfony\Component\HttpFoundation\RequestStack". Did you mean to call
e.g. "getCurrentRequest", "getMasterRequest" or "getParentRequest"?
First it’s not mandatory to declare your service help with
autoconfigure: true
Then you must inject
Symfony\Component\HttpFoundation\RequestStack
and make
$requestStack->getSession()
Here's the docs
https://symfony.com/doc/current/session.html
For user you inject
Symfony\Component\Security\Core\Security
and make
$security->getUser()
Here's the docs
https://symfony.com/doc/current/security.html#fetching-the-user-from-a-service
-- EDIT --
Prio symfony 5.3 session can directly be injected with
Symfony\Component\HttpFoundation\Session\SessionInterface
It's depreciated after. Here's the blog post :
https://symfony.com/blog/new-in-symfony-5-3-session-service-deprecation
Using symfony 6.2, you can also add this on your service.yml:
Symfony\Component\HttpFoundation\Session\SessionInterface:
factory: "#=service('request_stack').getCurrentRequest()?.getSession()"
It can return null value
Hi I'm starting to learn the symfony event system and to test it I created a "subscriber" that listens to the AuthenticationEvents::AUTHENTICATION_FAILURE event and dumps it.
I then tried to connect with fake data, but nothing happens.
I then tried to listen to another event KernelEvents::REQUEST and it works, so I don't see where the problem can come from.
MY SUBSCRIBER :
namespace App\EventSubscriber;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class AuthenticationSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
AuthenticationEvents::AUTHENTICATION_SUCCESS => 'securityauthenticationsuccess',
AuthenticationEvents::AUTHENTICATION_FAILURE => 'securityauthenticationfailure',
KernelEvents::REQUEST => 'KernelRequest',
];
}
public function securityauthenticationfailure(LoginFailureEvent $event){
dump($event);
}
public function securityauthenticationsuccess(LoginSuccessEvent $event){
dump($event);
}
public function KernelRequest(RequestEvent $event){
dump($event);
}
}```
MY SERVICE.YAML :
```# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
The Core\AuthenticationEvents class is part of the older authentication system. For the new HTTP based system, the event class name is used for the event name. So:
public static function getSubscribedEvents(): array
{
return [
LoginSuccessEvent::class => 'onLoginSuccess',
LoginFailureEvent::class => 'onLoginFailure',
];
}
Should get you one step further.
It might also be instructive to look at some of the listener classes under vendor\symfony\security-http\Authenticator\EventListener
By the way, I tested this code using an make:auth generated authenticator that extends AbstractLoginFormAuthenticator. All out of the box stuff. Works as expected.
I am trying to use the container.service_subscriber tag on my Controller to make some services available without injecting them through the constructor. In our project we don't want to use the autowiring and also can't use the autoconfigure option.
The structure of the Controller is as follow:
I have a base BaseController which extends from the AbstractFOSRestController of FOSRestBundle which has some common used methods for all my Controllers. That service will be used as parent for my other Controllers.
The service definition looks like this:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "#service1"
- "#service2"
- ...
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
#autowire: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
The class looks like this:
/**
* User controller.
*/
class UserController extends AbstractCRUDController implements ClassResourceInterface
{
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
'servicexyz' => ServiceXYZ::class,
]);
}
.......
}
The problem I have is, if I set autowire: false, it always automatically sets the full container and with this the appropriate deprecation message (as I am not setting it myself):
User Deprecated: Auto-injection of the container for "WM\ApiBundle\Controller\UserController" is deprecated since Symfony 4.2. Configure it as a service instead.
When setting autowire: true Symfony does respect the container.service_subscriber tag and only sets the partial container (ServiceLocator), which also would solve the deprecation message. I would have expected that autowiring should not make any differences in this case because I am explicitly telling the service which other services it should have.
Am I using the tags wrong or do I have a general problem in understanding how to subscribe a service to a Controller?
The basic issue is that the builtin service subscriber functionality will only inject the service locator into the constructor. A conventional controller which extends AbstractController uses autoconfigure to basically override this and uses setContainer instead of the constructor.
# ApiBundle/Resources/config/services.yaml
services:
_defaults:
autowire: false
autoconfigure: false
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
class UserController extends AbstractController
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
// ...
'logger' => LoggerInterface::class,
]);
}
public function index()
{
$url = $this->generateUrl('user'); // Works as expected
// $signer = $this->get('uri_signer'); // Fails as expected
$logger = $this->get('logger'); // Works as expected
return new Response('API Index Controller ' . get_class($this->container));
}
}
Results in:
API Index Controller Symfony\Component\DependencyInjection\Argument\ServiceLocator
Indicating that a service locator (as opposed to the global container is being injected).
You can also configure your service to use the setContainer method and eliminate the need for a constructor. Either approach will work.
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
calls: [['setContainer', ['#Psr\Container\ContainerInterface']]]
Solution to the problem is to extend the service definition of the Controller with a call to setContainer to inject the '#Psr\Container\ContainerInterface' service:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "#service1"
- "#service2"
- ...
calls:
- ['setContainer', ['#Psr\Container\ContainerInterface']]
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
This will give me a ServiceLocator as container containing only the regiestered services instead of the full container without using the autowire option.
Sidenote: Setting the #service_container would inject the full container.
For completeness, there was already an issue on the symfony project where this was discussed.
I am using symfony 4.2,
In .env file:
MAILER_URL=gmail://saurabhofficial:qwerty#localhost?encryption=tls&auth_mode=oauth
swiftmailer.yaml
swiftmailer:
url: '%env(MAILER_URL)%'
spool: { type: 'memory' }
Service:
namespace App\Service;
use App\Service\WelcomeMessage;
class WelcomeMail
{
private $welcomeMsgGenerator;
private $mailer;
public function __construct(WelcomeMessage $welcomeMsgGenerator, \Swift_Mailer $mailer)
{
$this->msg = $welcomeMsgGenerator;
$this->mailer = $mailer;
}
public function createMail()
{
$content = $this->msg->getWelcomeMessage();
$message = (new \Swift_Message('Saurabh!'))
->setFrom('saurabhofficial#gmail.com')
->setTo('******#gmail.com')
->addPart(
'Message'.$content
);
return $this->mailer->send($message) > 0;
}
}
Controller:
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;
use App\Service\WelcomeMail;
use Symfony\Component\HttpFoundation\Response;
class ServicesController extends Controller
{
/**
* #Route("/services", name="services")
*/
public function sendMailUsingServices(WelcomeMail $welcomeMail)
{
if($welcomeMail->createMail()){
$show = 'Check your Mail';
}
else{
die('Not working');
}
return $this->render('services/index.html.twig', [
'show' => $show,
]);
}
}
if($welcomeMail->createMail()){
$show = 'Check your Mail';
}
Above code gives me 'true', but mail mail is not sent/received. What am I doing wrong? Is any another way to implement it in SYMFONY 4?
Update
services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
config/packages/dev/swiftmailer.yaml
swiftmailer:
# send all emails to a specific address
#delivery_addresses: ['me#example.com']
Try to use your full e-mail address instead of just the username (ie, saurabhofficial).
Also, please check e-mail delivery is not disabled in config/packages/dev/swiftmailer.yaml (I assume you are using dev environment).
This is my solution .
Steps:
connection SMTP with google
define as a service
And to use it I call it from the controller
It works perfect on symfony 4: https://stackoverflow.com/a/55542824/2400373
i spent a lot of time but i did not solve it
I would like to pass LoggerInterface in the action of my controller
here is my service.yml
# Learn more about services, parameters and containers at
# https://symfony.com/doc/current/service_container.html
parameters:
#parameter_name: value
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
# makes classes in src/AppBundle available to be used as services
# this creates a service per class whose id is the fully-qualified class name
AppBundle\:
resource: '../../src/AppBundle/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/AppBundle/{Entity,Repository,Tests}'
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
# AppBundle\Controller\ArticlesController:
appbundle.form.type.articles:
class: AppBundle\Controller\ArticlesController
autowire: false
tags: ['controller.service_arguments']
here is my controller
<?php
//AppBundle\Controller\ArticlesController.php
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use AppBundle\Dto\ArticlesRequest;
use AppBundle\Form\ArticlesType;
use Psr\Log\LoggerInterface;
class ArticlesController extends Controller
{
public function ListAction()
{
return $this->render("#App/Articles/list.html.twig");
}
public function CreateAction(Request $request, LoggerInterface $logger)
{
$createArticleRequest = new ArticlesRequest();
$form = $this->createForm(ArticlesType::class, $createArticleRequest);
return $this->render("#App/Articles/create.html.twig", array('form' => $form->createView()));
//return $this->render("#App/Articles/create.html.twig");
}
public function EditAction()
{
return $this->render("#App/Articles/edit.html.twig");
}
public function DeleteAction()
{
return $this->render("#App/Articles/delete.html.twig");
}
}
This is the error message:
Controller "AppBundle\Controller\ArticlesController::CreateAction()" requires that you provide a value for the "$logger" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
In your services.yml under appbundle.form.type.articles, you need to pass autowire to true. Otherwise symfony cannot inject the dependencies in your service.
If you add a null default value as suggested above, you will not be able to log anything since you cannot log into a null. And if you do not want to log anything, simply remove the LoggerInterface from your parameters.
i resolve whit this code
instead of
public function CreateAction(Request $request, EntityManagerInterface $logger)
this
public function CreateAction(Request $request, EntityManagerInterface $logger = null)
add only = null