How to inject RabbitMqBundle producer into service? - php

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

Related

Problem with autowiring RegistryInterface in ServiceEntityRepository

I was following Symfony docs and wanted to use ServiceEntityRepository (link).
I created entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\BetRepository")
*/
class Bet
{
....
Repository was automatically created:
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
use App\Entity\Bet;
class BetRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Bet::class);
}
}
I wanted to create BetService which uses BetRepository:
namespace App\Service;
use App\Repository\BetRepository;
final class BetService
{
private $betRepository;
public function __construct(BetRepository $betRepository)
{
$this->betRepository = $betRepository;
}
finally I created a controller and wanted to inject BetService into it:
namespace App\Controller;
use App\Service\BetService;
final class BetController extends AbstractController
{
private $betService;
public function __construct(BetService $betService)
{
$this->betService = $betService;
}
}
Problem is I keep getting error:
Cannot autowire service "App\Repository\BetRepository": argument
"$registry" of method "__construct()" references interface
"Symfony\Bridge\Doctrine\RegistryInterface" but no such service
exists. Did you create a class that implements this interface?
my services.yaml is default one from installation:
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.
public: false
# 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']
I am using Symfony 4.2
I tried autowiring EntityManager to BetRepository however I get error that EntityManager constructor is not public and tbh I feel this is not the right approach as docs say it should work out of box.
I can provide composer.json (didn't initially as already question is quite long)
Thanks everyone in advance!

Symfony 3 and Swift Mailer: how to configure service

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.

Doctrine manager class injection to controller doesn't work

In my bundle I need to initialize my doctrine manager class (as a service and using ManagerRegistry) in constructor of controller, but symfony still throws this exception:
Type error: Too few arguments to function AdminBundle\Controller\RegistraceController::__construct(), 0 passed in C:\apache\htdocs\mujProjekt\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\Controller\ControllerResolver.php on line 198 and exactly 1 expected
Controller:
namespace AdminBundle\Controller;
use AdminBundle\Manager\AdminManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* Class DefaultController
* #package AdminBundle\Controller
* #Route("/registrace")
*/
class RegistraceController extends Controller
{
/**
* #var AdminManager
*/
private $manager;
public function __construct(AdminManager $manager)
{
$this->manager = $manager;
}
...
AdminManager:
namespace AdminBundle\Manager;
use AdminBundle\Entity\Uzivatel;
use Doctrine\Common\Persistence\ManagerRegistry;
class AdminManager
{
private $em;
public function __construct(ManagerRegistry $Doctrine)
{
$this->em = $Doctrine->getManager('default');
}
...
AdminBundle\Resources\config\services.yml :
services:
# admin.example:
# class: AdminBundle\Example
# arguments: ["#service_id", "plain_value", "%parameter%"]
admin.admin_manager:
class: AdminBundle\Manager\AdminManager
arguments:
["#doctrine"]
I tried to clear cache, but no success. The services.yml from AdminBundle is correctly included in config.yml.
orm config in config.yml:
orm:
auto_generate_proxy_classes: '%kernel.debug%'
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
AdminBundle: ~
I'm using Symfony 3.3 and PHP 7.1.
if you want to inject your AdminManager in your RegistraceController, you have to define the RegistraceController as a service. Look at https://symfony.com/doc/current/controller/service.html. There are some drawbacks of this approach, because you do not inherit from Symfony‘s base Controller. So, you have to inject the Router and the Template Engine too, if you need them. But I like defining my Controller as services. It‘s much cleaner when you see dependencies.
Instead of this, you can use the Symfony Container inside your controller as an Inversion Of Controll Container and get your service with $this->get('admin.admin_manager'); from inside your action.
So i think your service yml need to look like that:
services:
admin.admin_manager:
class: AdminBundle\Manager\AdminManager
arguments: ["#doctrine"]
admin.admin_controller:
class: AdminBundle\Controller\RegistraceController
arguments: ["#admin.admin_manager"]
Look up here Symfony Service Container
Hope it will help!
Greetings :)
Thank you all for your replies. Fortunately I solved my problem by adding this to services in app/config/services.yml.
AdminBundle\Controller\:
resource: '%kernel.project_dir%/src/AdminBundle/Controller'
public: true
tags: ['controller.service_arguments']

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.

Symfony2: How to access to service from repository

I have class ModelsRepository:
class ModelsRepository extends EntityRepository
{}
And service
container_data:
class: ProjectName\MyBundle\Common\Container
arguments: [#service_container]
I want get access from ModelsRepository to service container_data. I can't transmit service from controller used constructor.
Do you know how to do it?
IMHO, this shouldn't be needed since you may easily break rules like SRP and Law of Demeter
But if you really need it, here's a way to do this:
First, we define a base "ContainerAwareRepository" class which has a call "setContainer"
services.yml
services:
# This is the base class for any repository which need to access container
acme_bundle.repository.container_aware:
class: AcmeBundle\Repository\ContainerAwareRepository
abstract: true
calls:
- [ setContainer, [ #service_container ] ]
The ContainerAwareRepository may looks like this
AcmeBundle\Repository\ContainerAwareRepository.php
abstract class ContainerAwareRepository extends EntityRepository
{
protected $container;
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
}
}
Then, we can define our Model Repository.
We use here, the doctrine's getRepository method in order to construct our repository
services.yml
services:
acme_bundle.models.repository:
class: AcmeBundle\Repository\ModelsRepository
factory_service: doctrine.orm.entity_manager
factory_method: getRepository
arguments:
- "AcmeBundle:Models"
parent:
acme_bundle.repository.container_aware
And then, just define the class
AcmeBundle\Repository\ModelsRepository.php
class ModelsRepository extends ContainerAwareRepository
{
public function findFoo()
{
$this->container->get('fooservice');
}
}
In order to use the repository, you absolutely need to call it from the service first.
$container->get('acme_bundle.models.repository')->findFoo(); // No errors
$em->getRepository('AcmeBundle:Models')->findFoo(); // No errors
But if you directly do
$em->getRepository('AcmeBundle:Models')->findFoo(); // Fatal error, container is undefined
I tried some versions. Problem was solved follows
ModelRepository:
class ModelRepository extends EntityRepository
{
private $container;
function __construct($container, $em) {
$class = new ClassMetadata('ProjectName\MyBundle\Entity\ModelEntity');
$this->container = $container;
parent::__construct($em, $class);
}
}
security.yml:
providers:
default:
id: model_auth
services.yml
model_auth:
class: ProjectName\MyBundle\Repository\ModelRepository
argument
As a result I got repository with ability use container - as required.
But this realization can be used only in critical cases, because she has limitations for Repository.
Thx 4all.
You should never pass container to the repository, just as you should never let entities handle heavy logic. Repositories have only one purpose - retrieving data from the database. Nothing more (read: http://docs.doctrine-project.org/en/2.0.x/reference/working-with-objects.html).
If you need anything more complex than that, you should probably create a separate (container aware if you wish) service for that.
I would suggest using a factory service:
http://symfony.com/doc/current/components/dependency_injection/factories.html
//Repository
class ModelsRepositoryFactory
{
public static function getRepository($entityManager,$entityName,$fooservice)
{
$em = $entityManager;
$meta = $em->getClassMetadata($entityName);
$repository = new ModelsRepository($em, $meta, $fooservice);
return $repository;
}
}
//service
AcmeBundle.ModelsRepository:
class: Doctrine\ORM\EntityRepository
factory: [AcmeBundle\Repositories\ModelsRepositoryFactory,getRepository]
arguments:
- #doctrine.orm.entity_manager
- AcmeBundle\Entity\Models
- #fooservice
Are you sure that is a good idea to access service from repo?
Repositories are designed for custom SQL where, in case of doctrine, doctrine can help you with find(),findOne(),findBy(), [...] "magic" methods.
Take into account to inject your service where you use your repo and, if you need some parameters, pass it directly to repo's method.
I strongly agree that this should only be done when absolutely necessary. Though there is a quite simpler approach possible now (tested with Symfony 2.8).
Implement in your repository "ContainerAwareInterface"
Use the "ContainerAwareTrait"
adjust the services.yml
RepositoryClass:
namespace AcmeBundle\Repository;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use AcmeBundle\Entity\User;
class UserRepository extends EntityRepository implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function findUserBySomething($param)
{
$service = $this->container->get('my.other.service');
}
}
services.yml:
acme_bundle.repository.user:
lazy: true
class: AcmeBundle\Repository\UserRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments:
- "AcmeBundle:Entity/User"
calls:
- method: setContainer
arguments:
- '#service_container'
the easiest way is to inject the service into repository constructor.
class ModelsRepository extends EntityRepository
{
private $your_service;
public function __construct(ProjectName\MyBundle\Common\Container $service) {
$this->your_service = $service;
}
}
Extending Laurynas Mališauskas answer, to pass service to a constructor make your repository a service too and pass it with arguments:
models.repository:
class: ModelsRepository
arguments: ['#service_you_want_to_pass']

Categories