Symfony 3 and Swift Mailer: how to configure service - php

In my symfony project I try to configure an email controller without success.
services.yml
emailController:
class: AppBundle\Controller\emailController
public: true
arguments:
$mailer: '#mailer'
emailController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\UserBundle\FOSUserEvents;
use Doctrine\ORM\EntityManagerInterface;
class emailController extends Controller
{
protected $mailer;
function __construct(\Swift_Mailer $mailer) {
$this->mailer = $mailer;
}
public function sendMail($email){
$message = (new \Swift_Message())
->setSubject('send mail')
->setFrom('xx#yy.com')
->setTo($email)
->setBody('TEST')
->setContentType("text/html");
$this->mailer->send($message);
return 1;
}
}
Symfony return this message:
Catchable Fatal Error: Argument 1 passed to
AppBundle\Controller\emailController::__construct() must be an
instance of Swift_Mailer, none given,
I try some configuration and option but without success

I think the problem is, that you are configuring your controller as a service, but your router probably does not refer to the configured service, only to the class name.
You can use annotations:
#Route(service="emailController")
or the typical yaml format to refer to your controller as a service:
email:
path: /email
defaults: { _controller: emailController:indexAction }
Note that both refer to the service id specified in your definition above, not the actual class name. You can read more about the concept of controllers as services in the documentation: https://symfony.com/doc/current/controller/service.html
edit: As a sidenote since you seem to use a new Symfony version you might want to check out injecting services directly into actions using the resolve_controller_arguments tag: https://symfony.com/doc/current/controller.html#fetching-services-as-controller-arguments

You define a controller as a service which is not how it's intended to be but whatever; both controller and services are normal PHP classes.
Anyway, the aforementioned error message says it all, your service definition needs to supply the correct arguments (none given) e.g. like that:
arguments: ['#mailer']
Please try this.

Related

How to inject RabbitMqBundle producer into service?

I have a problem with injecting RabbitMq producer from RabbitMqBundle into my service.
Service:
namespace App\Service;
use Carbon\Carbon;
use Doctrine\ORM\EntityManagerInterface;
use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
class RatingPositionRecalculateService
{
protected $entityManager;
protected $positionProducer;
public function __construct(EntityManagerInterface $entityManager, ProducerInterface $producer)
{
$this->entityManager = $entityManager;
$this->positionProducer = $producer;
}
public function recalculate(Carbon $day)
{
// do stuff
}
}
old_sound_rabbit_mq.yml:
old_sound_rabbit_mq:
connections:
default:
url: ''
producers:
rating_position:
connection: default
exchange_options: { name: 'rating_position', type: direct }
consumers:
rating_position:
connection: default
exchange_options: {name: 'rating_position', type: direct}
queue_options: {name: 'rating_position'}
callback: rating_position_service
and I get:
Cannot autowire service "App\Service\RatingPositionRecalculateService": argument "$producer" of method "__construct()" references interface "OldSound\RabbitMqBundle\RabbitMq\ProducerInterface" but no such service exists. You should maybe alias this interface to the existing "old_sound_rabbit_mq.rating_position_producer" service. Did you create a class that implements this interface?
I've tried wiring using services.yml:
rating_position_recalculate_service:
class: App\Service\RatingPositionRecalculateService
autowire: false
arguments:
$entityManager: '#doctrine.orm.entity_manager'
$producer: '#old_sound_rabbit_mq.rating_position_producer'
but I still get the same exception.
If you only have one producer, you can define an alias like this in your services.yaml:
OldSound\RabbitMqBundle\RabbitMq\ProducerInterface: '#old_sound_rabbit_mq.rating_position_producer'
But i suggest you to create an empty class for your producer:
// src/Producer/RatingPositionProducer.php
<?php
namespace App\Producer;
class RatingPositionProducer extends \OldSound\RabbitMqBundle\RabbitMq\Producer
{
}
Then, in your old_sound_rabbit_mq.yml file:
old_sound_rabbit_mq:
...
producers:
rating_position:
class: App\Producer\RatingPositionProducer
...
In your services.yaml file:
App\Producer\RatingPositionProducer: '#old_sound_rabbit_mq.rating_position_producer'
And finally, in your RatingPositionRecalculateService class
// src/Service/RatingPositionRecalculateService.php
namespace App\Service;
use Carbon\Carbon;
use Doctrine\ORM\EntityManagerInterface;
use App\Producer\RatingPositionProducer;
class RatingPositionRecalculateService
{
...
public function __construct(EntityManagerInterface $entityManager, RatingPositionProducer $producer)
{
...
}
I don't think your problem is related to the bundle. Your service definition looks ok. They wrote it in the documentation:
Here we configure the connection service and the message endpoints that our application will have. In this example your service container will contain the service old_sound_rabbit_mq.upload_picture_producer and old_sound_rabbit_mq.upload_picture_consumer. The later expects that there's a service called upload_picture_service.
Which means you should have a service old_sound_rabbit_mq.rating_position_producer. You can verify it by using the command bin/console debug:cont --show-private | grep old_sound_rabbit_mq.
But the problem is probably somewhere else.
Here is what I guess:
You have a configuration file that registers all your classes as service
You have another config file for your RatingPositionRecalculateService
If it's true, then the error is due to the duplicated registration of your service. While registering all the services, Symfony DIC tries to register your RatingPositionRecalculateService but can't link it to the producer (because the producer is not registered in the same file). You may have a look at another problem which has a similar issue: External service configuration in Symfony 4

Symfony 2 calling non-existent service "router"

I have a service MailController which is defined like this in my config
services:
mail_controller:
class: Company\Project\Bundle\Controller\MailController
I'm calling the Service in other services
$mailController = $this->get('mail_controller');
Now the error i get is building up on this Question
The container wasn't set on the Controller, so i'm injecting one within the constructor
// MailController
public function __construct() {
$this->setContainer(new Container());
}
Now i'm getting this error:
You have requested a non-existent service "router".
I'm guessing that i need to inject further services whatsoever, but i don't know what to inject, so what do i need to further add so my Controller can work with all services?
My MailController looks like this
namespace Company\Project\Bundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\DependencyInjection\Container;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Doctrine\ORM\EntityManager;
class MailController extends Controller{
public function __construct() {
$this->setContainer(new Container());
}
//Code for mailstuff
}
You're creating a new container rather than injecting the built container so it has no services.
To use your controller you need to inject the pre made service container in to your controller through your service like so..
services:
mail_controller:
class: Company\Project\Bundle\Controller\MailController
calls:
- [ setContainer, [ #service_container ]]
.. and get rid of the setter in your __construct.
injecting the whole service container
calls: - [ setContainer, [ #service_container ]]
defeats the purpose of declaring your controller as a service.
Just inject the service(s) you need in your constructor. The constructor needs the service handed as an parameter and do not extend Controller anymore.
//MailController
use Symfony\Component\Routing\RouterInterface;
class MailController
{
private $router;
public function __construct(RouterInterface $router){
$this->router = $router;
}
//actions
}
Now you need to adjust your services.yml and extend the service with arguments describing the service you need
services:
mail_controller:
class: Company\Project\Bundle\Controller\MailController
arguments:
- #router
et voila,
only one service needed, only one service injected.
If you find yourself injecting too many services in one action, chances are your action/controller is not 'thin' enough.

Symfony 2 dependency injection

I'm learning symfony 2 dependency injection system. I'm trying to inject Response object in a controller.
ServiceController.php
namespace LD\LearnBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ServiceController extends Controller
{
public function indexAction(Response $response)
{
}
}
Here is the content of services.yml file (please note that it's included in app/config/config.yml
services:
ld_response:
class: Symfony\Component\HttpFoundation\Response
ld_bla:
class: LD\LearnBundle\ServiceController
arguments: ["#ld_response"]
When I try to access ServiceController I get
Class LD\LearnBundle\Controller\Response does not exist
500 Internal Server Error - ReflectionException
What I'm doing wrong?
There are 2 things wrong here:
1: "Class LD\LearnBundle\Controller\Response does not exist"
The class doesn't exist. You used Response without importing the namespace, so the error message is quite explicit here.
2: You shouldn't inject the response. It doesn't make any sense at all. The response is not a service, it's a value that should be passed down through method parameters.
Here's the fix:
namespace LD\LearnBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response; // was missing
class ServiceController extends Controller
{
public function indexAction()
{
return new Response(); // The Controller is responsible of creating the Response.
}
}
Generally, Class <current-namespace>\class does not exist errors hint to a missing use statement.
May I add that:
you shouldn't declare your services in the app/config/config.yml file (create a specific services.yml file. Even better: create it in a bundle)
you shouldn't inject the Response object: it is the responsibility of the controller to create it

How to use Namespaced Sessions in Symfony2

I am trying to use symfony2 sessions.I do this
$session = $this->getRequest()->getSession();
$session->set('token','value');
This works. But i want to use namespace in session. Documentation says
class NamespacedAttributeBag
provides that feature but i cannot figure out how to implement it
Just open your config.yml and after imports add:
parameters:
session.attribute_bag.class: Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag
It looks like this:
imports:
- { resource: parameters.yml }
- { resource: security.yml }
parameters:
session.attribute_bag.class: Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag
framework:
# ...
You should redefine session service and also define service for your attribute bag (if you'll check default implementation of session.attribute_bag you'll see that this service has only class attribute).
And inject your new service to redefined session service into there
services:
session:
class: Symfony\Component\HttpFoundation\Session\Session
arguments:
- #session.storage
- #your.session.attribute_bag #service id is defined below
- #session.flash_bag
your.session.attribute_bag:
class: Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag
Because it's also possible to use the HTTPFoundation Component outside of Symfony2, the way to implement NamespacedUserBags is as follows:
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag;
$session = new Session();
//first bag
$myAttributeBag = new NamespacedAttributeBag('<your_storage_key_1>');
$myAttributeBag->setName('<your_tag_name_1>');
$session->registerBag($myAttributeBag);
//second bag
$myAttributeBag = new NamespacedAttributeBag('<your_storage_key_2>');
$myAttributeBag->setName('<your_tag_name_2>');
$session->registerBag($myAttributeBag);
$session->start();
Register as many bags as you want, but make sure to do this before you start the session. Now you can switch between bags using getBag():
$activeBag = $session->getBag('<your_tag_name>');
and access the namespaced bag with the typical methods :
$activeBag->set('tokens/a', 'adsf82983asd');
$activeBag->set('tokens/b', 'daslfl232l3k');
print_r($activeBag->get('tokens'));
Since Symfony 3, the override of session.attribute_bag.class parameter doesn't work anymore.
The solution I applied after pulling my hair for a few time is using a compiler pass to override the session.attribute_bag service class.
I did it in the Kernel directly, but an external compiler pass would work the same way.
SF4 Kernel
<?php
// src/Kernel.php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag;
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
// ...
public function process(ContainerBuilder $container)
{
$container->getDefinition('session.attribute_bag')->setClass(NamespacedAttributeBag::class);
}
}
With Symfony 4 (and Flex), use the following configuration to use NamespacedAttributeBag:
# config/services.yaml
services:
session.attribute_bag:
class: Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag
# ...
Update: Namespaced Sessions were removed in Symfony 6.0
See https://symfony.com/doc/5.4/session.html#basic-usage
The NamespacedAttributeBag class is deprecated since Symfony 5.3. If you need this feature, you will have to implement the class yourself.

Symfony2 dependecy injection doesn't work with controller

According to Symfony2 Cookbook I'm trying to secure controller via dependecy injection, but I'm getting error Catchable Fatal Error: Argument 1 passed to Acme\ExampleBundle\Controller\DefaultController::__construct() must implement interface Symfony\Component\Security\Core\SecurityContextInterface, none given, called in /var/www/example/app/cache/dev/classes.php on line 4706 and defined in /var/www/example/src/Acme/ExampleBundle/Controller/DefaultController.php line 13
Here is my services.yml
parameters:
acme_example.default.class: Acme\ExampleBundle\Controller\DefaultController
services:
acme_example.default:
class: %acme_example.default.class%
arguments: [#security.context]
and controller:
namespace Acme\ExampleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class DefaultController extends Controller {
public function __construct(SecurityContextInterface $securityContext)
{
if(false === $securityContext->isGranted('IS_AUTHENTICATED_FULLY'))
{
throw new AccessDeniedException();
}
}
public function indexAction()
{
return new Response('OK');
}
}
If you configure your controllers as services you need to use a slightly different syntax when referencing them in your routes. Instead of AcmeExampleBundle:Default:index you should use acme_example.default:indexAction.
Make sure you use Symfony\Component\Security\Core\SecurityContextInterface; in your controller. Without it, the SecurityContextInterface type hint in the constructor won't resolve.
Also, make sure your controller is actually being called as a service. The error you posted is complaining that nothing was sent to the constructor, which sounds to me like you're using your controller the 'default' way. See this cookbook page on how to setup a controller as a service.
The class Symfony\Bundle\FrameworkBundle\Controller\Controller extends ContainerAware base class. This class ha whole the container accessible via $container local property, so you should not inject any services to a controller service, because you can access SecurityContext via $this->container->get('security.context').

Categories