Foreword: please disregard injecting the whole container and other unclean things, I just wanted to show not-working example, this is before refactor.
I have a service defined in YAML:
app.service.my_service:
class: App\Services\MyService
public: true
calls:
- [setContainer, ['#service_container']]
part of my service:
class MyService implements ContainerAwareInterface
{
/** #var ContainerInterface */
private $container;
/** #var EntityManagerInterface */
private $em;
public function setContainer(?ContainerInterface $container = null)
{
$this->container = $container;
$this->em = $container->get('doctrine')->getManager();
}
Then I have a controller, which have that service autowired in constructor, and not instantiaded from container:
class MyController
{
/**
* #var MyService
*/
private $my_service;
function __construct(MyService $my_service) {
$this->my_service = $my_service;
}
While it actually autowires service itself, it completely ignores the setContainer call, so I end up with empty container.
I wanted to avoid calling $this->get('app.service.my_service') everywhere and I can't simply call it once in controller's contructor, or call setContainer in the constructor on autowired service, because at that time container is empty.
Any chances I can do it in a clean way?
Since you already know injecting the container is a bad idea then I guess I'll spare you the lecture. I have not encountered this particular issue but you can try using the little known '#required' container directive and see if it helps.
/** #required */
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
$this->em = $container->get('doctrine.orm.default_entity_manager);
}
This is more of a guess than an answer but it is easy to try.
Update: It worked. Cool.
I just wanted to add that, in most cases, dependencies should be injected into the constructor. This avoids having partially initialized services. On the other hand, as long as you are generating your services via the container then it is not really a problem.
I find it to be very useful in traits. For example:
trait RouterTrait
{
private RouterInterface $router;
/** #required */
public function setRouter(RouterInterface $router)
{
$this->router = isset($this->router) ? $this->router: $router;
}
// These are just copied from AbstractController
protected function generateUrl(
return $this->router->generate(...
protected function redirectToRoute(
#
class SomeService
use RouterTrait;
public function someMethod()
$this->generateUrl('''
So if a service needs to do some routing then they just need to use the router trait and the router is automatically injected. Saves adding boilerplate code to the service's constructor. I do put a guard around setRouter so it can only be effectively called once which eliminates another concern about using setters for dependencies.
Update #2:
The problem with the original code is that the MyService was defined with a service is of app.service.my_service and not the class name. Autowire actually generated a second MyService and injected it since autowire looks for service ids that match the class name. Changing the service definition to:
App\Services\MyService:
public: true
calls:
- [setContainer, ['#service_container']]
Would have also worked. Or explicitly injecting app.service.my_service into the controller.
I am trying to override the FosUserBundle Registry Controller, but the following error appears:
Cannot autowire service "App\Controller\RegistrationController": argument "$formFactory" of method
"FOS\UserBundle\Controller\RegistrationController::__construct()"
references interface "FOS\UserBundle\Form\Factory\FactoryInterface" but no such service exists.
You should maybe alias this interface to one of these existing services: "fos_user.profile.form.factory", "fos_user.registration.form.factory", "fos_user.change_password.form.factory",
"fos_user.resetting.form.factory". Did you create a class that implements this interface?
I'm using Symfony4 and this is my RegistrationController.php code.
I've tried multiple ways, but I can't find a way to make it work.
class RegistrationController extends BaseController
{
public function registerAction(Request $request)
{
$form = $this->get('fos_user.registration.form.factory');
$formHandler = $this->get('fos_user.registration.form.handler');
$confirmationEnabled = $this->getParameter('fos_user.registration.confirmation.enabled');
die("Hello");
$process = $formHandler->process($confirmationEnabled);
if ($process) {
$user = $form->getData();
if ($confirmationEnabled) {
$this->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
$route = 'fos_user_registration_check_email';
} else {
// $this->authenticateUser($user);
$route = 'users_edit';
}
$this->setFlash('fos_user_success', 'registration.flash.user_created');
$url = $this->get('router')->generate($route, array("id" => $user->id));
return new RedirectResponse($url);
}
return $this->get('templating')->renderResponse('FOSUserBundle:Registration:register.html.twig', array(
'form' => $form->createView()
));
}
}
?>
I had the same problem , with SF4 and FOSUserBundle. After looking around for half a day I come with a solution. I use some material from #thibaut and adapt it to SF 4.2.
I inject the services needed by fos user registration controlleur and give FOS\UserBundle\Form\Factory\FactoryInterface an alias
config/services.yaml
app.controller.register:
class: App\Controller\bundles\FOSUserBundle\RegistrationController
arguments:
$eventDispatcher: '#event_dispatcher'
$formFactory: '#fos_user.registration.form.factory'
$userManager: '#fos_user.user_manager'
$tokenStorage: 'security.token_storage'
calls:
- method: setContainer
arguments:
- '#service_container'
public: true
FOS\UserBundle\Form\Factory\FactoryInterface:
alias: 'fos_user.registration.form.factory'
public: true
Then in my registration controller, I redeclare a constructor and save formfactory to be use in the controller
App\Controller\bundles\FOSUserBundle
protected $formFactory;
public function __construct(EventDispatcherInterface $eventDispatcher, FactoryInterface $formFactory, UserManagerInterface $userManager, TokenStorageInterface $tokenStorage, $serviceContainer=null)
{
$this->setContainer($serviceContainer);
$this->formFactory = $formFactory;
parent::__construct($eventDispatcher, $formFactory, $userManager, $tokenStorage);
}
use the declare $formfactory in my action
/**
* #Route("/register", name="registration")
*/
public function registerAction(Request $request)
{
/** #var $formFactory FactoryInterface */
$formFactory = $this->formFactory;
/** #var $userManager UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
/** #var $dispatcher EventDispatcherInterface */
$dispatcher = $this->get('event_dispatcher');
Hope it also help you guys
work in symfony 4.2
services.yaml
App\Controller\RegistrationController:
arguments:
$formFactory: '#fos_user.registration.form.factory'
and controller
use FOS\UserBundle\Controller\RegistrationController as Base;
class RegistrationController extends Base
{
private $eventDispatcher;
private $formFactory;
private $userManager;
private $tokenStorage;
public function __construct(EventDispatcherInterface $eventDispatcher, FactoryInterface $formFactory, UserManagerInterface $userManager, TokenStorageInterface $tokenStorage)
{
parent::__construct($eventDispatcher, $formFactory,$userManager, $tokenStorage);
$this->eventDispatcher = $eventDispatcher;
$this->formFactory = $formFactory;
$this->userManager = $userManager;
$this->tokenStorage = $tokenStorage;
}
I don't know which version of FOSUserBundle are you using but there is no official support for sf4 yet - see release notes. You can try dev-master version and follow this issue to make it work.
You should use hooks and avoid override controllers in FOSUserBundel v2.x
More information you can read on the page: http://symfony.com/doc/master/bundles/FOSUserBundle/controller_events.html
I had to use an alias for the FOS services that were refusing to autowire in my config/services.yaml
FOS\UserBundle\Form\Factory\FormFactory: '#fos_user.registration.form.factory'
As seen in vendor/friendsofsymfony/user-bundle/Resources/config/registration.xml
Hello i think i have found a way to handle this problem:
Define your service:
app.controller.register:
class: AppBundle\Controller\RegisterController
arguments: ['#service_container']
public: true
then your controller
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use FOS\UserBundle\Controller\RegistrationController as BaseController;
/**
* #Route(service="app.controller.register")
*/
class RegisterController extends BaseController
{
public function __construct($serviceContainer=null)
{
$this->setContainer($serviceContainer);
$eventDispatcher=$this->container->get('event_dispatcher');
$formFactory=$this->container->get('fos_user.registration.form.factory');
$userManager=$this->container->get('fos_user.user_manager');
$tokenStorage=$this->container->get('security.token_storage');
parent::__construct($eventDispatcher, $formFactory, $userManager, $tokenStorage);
}
/**
* #Route("/register", name="register")
*/
public function registerAction(Request $request)
{
//do your stuff
}
}
hope this help (sorry for bad english)
To solve this issue in an override:
Do a full override of the FOSUserBundle RegistrationController i.e. copy its entire contents to AppBundle/Controller/RegistrationController.php
Then replace the __construct function with the below:
public function __construct() {
$this->eventDispatcher = $this->get('event_dispatcher');
$this->formFactory = $this->get('fos_user.registration.form.factory');
$this->userManager = $this->get('fos_user.user_manager');
$this->tokenStorage = $this->get('security.token_storage'); }
Voila! You are just calling these services in the constructor instead of passing them in as parameters in a registration controller service.
I did this now & it works for sf3.4.
I had the same problem , with SF4, FOSUserBundle (behind SonataAdmin).
I wanted listen an event : Registration confirmed, to send a message to the admininstrator.
my first try : override the FosUser controller. But the issues are many !
Thanks to #majne, I went to http://symfony.com/doc/master/bundles/FOSUserBundle/controller_events.html, and, even I am not in 'master', that works, hooking the event, from FOS\UserBundle\FOSUserEvents class.
I had some problems to cleary adapt the use statements...
With Symfony 4.2 the following should work.
Contrary to what is suggested in other answers, there was no need to extend the corresponding FOSUserBundle controller or modify the __construct method.
/config/services.yaml
FOS\UserBundle\Form\Factory\FactoryInterface: '#fos_user.registration.form.factory'
In my case I actually overrode both the RegistrationController and the ResettingController and in that case one needs to wire both forms - and also the MailerInterface - as so:
/config/services.yaml
FOS\UserBundle\Form\Factory\FactoryInterface: '#fos_user.resetting.form.factory'
FOS\UserBundle\Mailer\MailerInterface: '#fos_user.mailer.default'
App\Controller\RegistrationController:
autowire: true
arguments:
$formFactory: '#fos_user.registration.form.factory'
I had the same problem and I solve it by making "fos_user.resetting.form.factory" public
#fos_user.yaml
services:
new_service_name:
alias: fos_user.registration.form.factory
public: true
Then use the service with the new name
$form = $this->get('new_service_name');
I want to create HelperController for my project. I generate a controller with doctrine:generate:controller and I need to use entity manager in it.
I enjected to services.yml but it is giving an error like this:
Argument 1 passed to CampingBundle\Controller\HelperController::__construct() must be an instance of Doctrine\ORM\EntityManager, none given ...
My Controller Code :
namespace CampingBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Doctrine\ORM\EntityManager;
class HelperController extends Controller
{
protected $manager;
public function __construct(EntityManager $manager)
{
$this->manager = $manager;
}
My Services.yml :
services:
camping.helper_controller:
class: CampingBundle\Controller\HelperController
arguments: ["#doctrine.orm.entity_manager"]
Why it doesn't work ? Shoudl I clear cache or something else or is there anything wrong in definition ?
Thanks
Try to use EntityManagerInterface and remove extends Controller.
Check this link if you need CAS (Controllers as Services).
Change protected $manager; to private $manager;
namespace CampingBundle\Controller;
use Doctrine\ORM\EntityManagerInterface;
class HelperController
{
/**
* #var EntityManagerInterface $entityManager
*/
private $entityManager;
/**
* #param $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
}
I'll leave my two cents here as I had same issue and fixed it by adding tags to service.
something.validate.some_service:
class: Path\To\Some\Validator
arguments:
- '#doctrine.orm.entity_manager'
tags:
- { name: validator.constraint_validator, alias: some_validator_alias }
How to Work with Service Tags by Symfony
I got a strange problem using the logger service in symfony 2:
When injecting the logger to a service, I get a type error because LoggerInterface expected but Symfony\Bridge\Monolog\Logger given.
Also if I try to inject a custom logger, I get error because of undefined service.
Here is my code:
confiy.yml
monolog:
channels: ['payment']
handlers:
paymentlog:
type: stream
path: "%kernel.logs_dir%/payment.log"
level: debug
channels: [payment]
service.yml
#payment_services
payment.gateway_payments:
class: AppBundle\Service\paymentService
arguments: ["#service_container", "#doctrine.orm.entity_manager", "#logger"]
Service:
<?php
namespace AppBundle\Service;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
class paymentService {
private $container;
private $em;
private $logger;
public function __construct(ContainerInterface $container, EntityManager $em, LoggerInterface $logger){
$this->container = $container;
$this->em = $em;
$this->logger = $logger;
}
Also injecting the logger with #monolog.logger.paymentlog is giving me an error "undefinded service"
Can someone please tell me where I am wrong?
THX a lot.
try this:
use Monolog\Logger;
instead of this:
use Symfony\Component\HttpKernel\Log\LoggerInterface;
And after this;
public function __construct(ContainerInterface $container, EntityManager $em, Logger $logger){
insetad of this:
public function __construct(ContainerInterface $container, EntityManager $em, LoggerInterface $logger){
I needed to move my model from the controller method, so I got help to change it to a service. The service by itself works, but I need to be able to connect to doctrine and the kernel from inside of this service. At first I tried to enable doctrine, but that created problems. How can I make this work? I've followed docs and got this code. I have no idea why I got the error below. Thank you for your help in advance.
My config is:
CSVImport.php
namespace Tools\TFIBundle\Model;
use Doctrine\ORM\EntityManager;
class CSVImport {
protected $em;
public function __construct( EntityManager $em ) {
$this->em = $em;
}
app/config/config.yml
services:
csvimport:
class: Tools\TFIBundle\Model\CSVImport
arguments: [ #doctrine.orm.entity_manager ]
action in controller
$cvsimport = $this->get('csvimport');
MY ERROR
Catchable Fatal Error: Argument 1 passed to
Tools\TFIBundle\Model\CSVImport::__construct() must be an instance of
Doctrine\ORM\EntityManager, none given, called in
.../Tools/TFIBundle/Controller/DefaultController.php on line 58 and defined in
.../Tools/TFIBundle/Model/CSVImport.php line 12
EDIT, my working code:
service class code with Kernel attached to it
namespace Tools\TFIBundle\Model;
use Doctrine\ORM\EntityManager,
AppKernel;
class CSVImport {
protected $em;
protected $kernel;
protected $cacheDir;
public function __construct( EntityManager $em, AppKernel $k ) {
$this->em = $em;
$this->kernel = $k;
}
Try injecting #doctrine.orm.default_entity_manager.
On web I've found how to connect to Doctrine DBAL to be able to make queries on my own. But when i changed my configuration to this one:
app/config.yml
services:
csvimport:
class: Tools\TFIBundle\Model\CSVImport
arguments: [ #doctrine.dbal.connection, #doctrine.orm.entity_manager, #kernel ]
class definition
namespace Tools\TFIBundle\Model;
use Doctrine\ORM\EntityManager,
Doctrine\DBAL\Connection,
AppKernel;
class CSVImport {
protected $c;
protected $em;
protected $kernel;
public function __construct(Connection $c, EntityManager $em, AppKernel $k ) {
$this->c = $c;
$this->em = $em;
$this->kernel = $k;
}
i got error:
RuntimeException: The definition "csvimport" has a reference to an abstract definition "doctrine.dbal.connection". Abstract definitions cannot be the target of references.
Any ideas?