Injecting Service Factory - symfony

Injecting a factory service builder (#subprocessor.builder_factory) into another service (processor)
services:
processor:
class: Project\ServiceBundle\Services\Processor
arguments: [#doctrine.orm.entity_manager,%kernel.logs_dir%,#subprocessor.builder_factory]
subprocessor.builder_factory:
class: Project\ServiceBundle\Services\Builders\BuilderFactory
arguments: [#event_dispatcher,#doctrine.orm.entity_manager,%kernel.logs_dir%]
processor.alfa:
class: 'Project\ServiceBundle\Services\Builders\Alpha'
factory: ['Project\ServiceBundle\Services\Builders\BuilderFactory:__factoryMethod']
tag: ['builder']
processor.beta:
class: 'Project\ServiceBundle\Services\Builders\Beta'
factory: ['Project\ServiceBundle\Services\Builders\BuilderFactory:__factoryMethod']
tag: ['builder']
The factory implements the 'process' method to load the tagged ones:
public function process(ContainerBuilder $container)
{
$widgets = $container->findTaggedServiceIds('builder');
foreach ($widgets as $id => $tags) {
$definition = $container->getDefinition($id);
$definition->setArguments([$definition->getClass()]);
}
}
how do I get the proper service from #processor service?

Related

Cannot autowire service in Symfony 3.4 and FosUserBundle

I try to override REGISTRATION_SUCCESS in FosUserBundle to redirect the admin on user's list after register a new user.
So I have created a new event subscriber :
<?php
namespace AppBundle\EventListener;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Mailer\MailerInterface;
use FOS\UserBundle\Util\TokenGeneratorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class RedirectAfterRegistrationSubscriber implements EventSubscriberInterface
{
private $mailer;
private $tokenGenerator;
private $router;
public function __construct(MailerInterface $mailer, TokenGeneratorInterface $tokenGenerator, UrlGeneratorInterface $router)
{
$this->mailer = $mailer;
$this->tokenGenerator = $tokenGenerator;
$this->router = $router;
}
public function onRegistrationSuccess(FormEvent $event)
{
$user = $event->getForm()->getData();
$user->setEnabled(false);
if (null === $user->getConfirmationToken()) {
$user->setConfirmationToken($this->tokenGenerator->generateToken());
}
$this->mailer->sendConfirmationEmailMessage($user);
$url = $this->router->generate('user_index');
$event->setResponse(new RedirectResponse($url));
}
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess'
];
}
}
and the following service :
app.redirect_after_registration_subscriber:
class: AppBundle\EventListener\RedirectAfterRegistrationSubscriber
arguments: ['#fos_user.mailer', '#fos_user.util.token_generator', '#router']
tags:
- { name: kernel.event_subscriber }
I don't understand why this error appears :
Cannot autowire service "AppBundle\EventListener\RedirectAfterRegistrationSubscriber":
argument "$mailer" of method "__construct()" references interface
"FOS\UserBundle\Mailer\MailerInterface" but no such service exists. You should maybe alias
this interface to one of these existing services: "fos_user.mailer.default",
"fos_user.mailer.twig_swift", "fos_user.mailer.noop".
I suppose you are using autodiscovering of services. Something like:
# services.yaml
AppBundle\:
resource: '../src/'
...
So in addition to the #app.redirect_after_registration_subscriber that you define, Symfony defines another service with id #AppBundle\EventListener\RedirectAfterRegistrationSubscriber. Both point to AppBundle\EventListener\RedirectAfterRegistrationSubscriber class. Yet you configured the mailer parameter only for the first one.
The solution:
AppBundle\EventListener\RedirectAfterRegistrationSubscriber:
arguments: ['#fos_user.mailer', '#fos_user.util.token_generator', '#router']
tags:
- { name: kernel.event_subscriber }
With autowiring and autoconfigure you can even sypmlify to:
AppBundle\EventListener\RedirectAfterRegistrationSubscriber:
arguments:
$mailer: '#fos_user.mailer'

Call container inside ExceptionListener

I am using Symfony and i have created custom ExceptionListener to handle error.
class ExceptionListener
{
protected $templating;
protected $kernel;
public function __construct(EngineInterface $templating, $kernel)
{
$this->templating = $templating;
$this->kernel = $kernel;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
// exception object
$exception = $event->getException();
// new Response object
$response = new Response();
$response->setContent(
// create you custom template AcmeFooBundle:Exception:exception.html.twig
$this->templating->render(
'Exception/exception.html.twig',
array('exception' => $exception)
)
);
// HttpExceptionInterface is a special type of exception
// that holds status code and header details
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->replace($exception->getHeaders());
} else {
$this->container->get('monolog.logger.db')->info('something happened 34', [
'foo' => 'bar'
]);
$response->setStatusCode(500);
}
if($exception instanceof FatalThrowableError){
return $this->templating->render(
'Exception/exception.html.twig'
);
}
// set the new $response object to the $event
$event->setResponse($response);
}
}
and in service
kernel.listener.acme_foo_exception_listener:
class: AppBundle\Listener\ExceptionListener
arguments: [#templating, #kernel]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
My aim is to when symfony throws exception i need to log error in database so i have created Logger event as per below link and it works fine when i called in controller but this event doesn't work when i called inside ExceptionListener.
I got following error
Notice: Undefined property:
AppBundle\Listener\ExceptionListener::$container in
can any one help me how i can pass container inside Listener
As said by geoforce your service doesn't know about the container. Quick fix for this by changing the service arguments:
arguments: [#templating, #container]
While changing the listener constructor to:
public function __construct(EngineInterface $templating, ContainerInterface $container)
{
$this->container = $container;
// ...
This should work, but injecting the entire container is quite an overkill and should definitely be done differently. Inject just what you need:
arguments: [#templating, '#monolog.logger.db']
And your constructor:
public function __construct(EngineInterface $templating,
LoggerInterface $logger)
{
$this->logger = $logger;
// ...
Log with $this->logger->info(...).
Since you've said that you're new to Symfony, I'd heavily recommend reading the DI component (http://symfony.com/doc/current/components/dependency_injection.html) docs. Understanding what DI does and how it works is mandatory to work with MVC frameworks like Symfony.
Like the error says, you are trying to access a property that does not exist:
$this->container->get('monolog.logger.db')->info('something happened 34', [
'foo' => 'bar'
]);
The container property is never declared nor assigned. If you want to access your logging service inject it in your service definition, like you did with the templating and kernel services.
Updated service definition:
kernel.listener.acme_foo_exception_listener:
class: AppBundle\Listener\ExceptionListener
arguments: [#templating, #kernel, #monolog.logger.db]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
And update your class constructor to accept the log service as the third argument.

Inject Doctrine Repository into controller

I had the following controller:
<?php
namespace AppBundle\Controller;
use AppBundle\Controller\BaseController;
use Symfony\Component\HttpFoundation\Request;
class UserController extends BaseController
{
public function allAction(Request $request)
{
$users = $this->getDoctrine()
->getRepository('AppBundle:User')
->findAll();
return $this->respond(['users' => $users], 200);
}
}
And I would like to inject the repository into the controller (for testing purposes)
Controller:
<?php
namespace AppBundle\Controller;
use AppBundle\Controller\BaseController;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\HttpFoundation\Request;
class UserController extends BaseController
{
private $userrepository;
public function __construct(EntityRepository $userrepository)
{
$this->userrepository = $userrepository;
}
public function allAction(Request $request)
{
$users = $this->userrepository->findAll();
return $this->respond(['users' => $users], 200);
}
}
services.yml
services:
userrepository:
class: Doctrine\ORM\EntityRepository
factory_service: doctrine.orm.entity_manager
factory_method: getRepository
arguments:
- AppBundle\Entity\User
usercontroller:
class: AppBundle\Controller\UserController
arguments:
- "#userrepository"
routing.yml
api_users_all:
path: /api/users.{_format}
defaults: { _controller: usercontroller:allAction, _format: json }
requirements:
_method: GET
I keep facing the following error:
PHP Fatal error: Call to a member function get() on a non-object in /Applications/MAMP/htdocs/api/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php on line 350
line 350
return $this->container->get($id);
So my guess is that the controller is no longer 'ContainerAware' or something like that.
But I can't seem to figure this out.
I am working with symfony 2.7
After you configure the router to call controller via service, controller is not created directly but requested from the Dependency Injection container. And yes, there is no longer container because you inject only one parameter to it: user repository.
If you want whole container (which is not recommended) and you extending Symfony Controller in your BaseController set it in your service:
services:
usercontroller:
class: AppBundle\Controller\UserController
calls:
- [setContainer, ["#service_container"]]
arguments:
- "#userrepository"
If you not extending Symfony Controller, inject container as argument:
services:
usercontroller:
class: AppBundle\Controller\UserController
arguments:
- "#userrepository"
- "#service_container"
Controller:
class UserController extends BaseController
{
private $userrepository;
private $container;
public function __construct(EntityRepository $userrepository, $container)
{
$this->userrepository = $userrepository;
$this->container = $container;
}
It's not recommended, inject only what you want eg. if you use in your controller only repository and mailer inject only them not the whole container.

#mailer and #twig in argument of a service error ServiceCircularReferenceException

I'm trying to put twig like argument of my service but i have always the same error :
ServiceCircularReferenceException in bootstrap.php.cache line 2129
Circular reference detected for service "doctrine.orm.default_entity_manager",path: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> wh.participant_listener -> wh.participant_notification -> twig -> security.authorization_checker -> security.authentication.manager -> fos_user.user_provider.username -> fos_user.user_manager".`
This is my service.yml file
wh.participant_notification:
class: WH\TrainingBundle\Notification\Notification
arguments: [#mailer, #twig]
wh.participant_listener:
class: WH\TrainingBundle\EventListener\ParticipantListener
arguments: [#wh.participant_notification]
tags:
- { name: doctrine.event_listener, event: postUpdate }
- { name: doctrine.event_listener, event: postPersist }
My PartcicipantListenerFile
namespace WH\TrainingBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use WH\TrainingBundle\Notification\Notification;
class ParticipantListener
{
protected $notification;
public function __construct(Notification $notification)
{
$this->notification = $notification;
}
}
This probleme exist only when i pass #wh.participant_notificationin arguments of my second service
Any body has an idea ?
Thank's a lot
I've find a solution, not pretty, but it works :
First i pass the service container in argument of my service
services:
wh.participant_notification:
class: WH\TrainingBundle\Notification\Notification
arguments: ['#service_container']
wh.participant_listener:
class: WH\TrainingBundle\EventListener\ParticipantListener
arguments: ['#wh.participant_notification']
tags:
- { name: doctrine.event_listener, event: postPersist }
then in my Notification.php class :
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
private $container;
public function __construct(Container $container) {
$this->container = $container;
}
public function subscribValidation ($participant) {
$templating = $this->container->get('templating');
$mailer = $this->container->get('mailer');
...
I can't create protected var $twig because the probleme persiste.
I repeat, its only with twig service (or template).
Maybe another one find a better solution ...
The circular message, while unclear, should guide you.
Doctrine entity manager loads its listeners,wh.participant_notification among them. Your service requires twig which in turns requires a chain of other things, doctrine entity manager among them. This causes the exception above.
One solution to this issue could be to use setter injection
So you can just define your service as:
wh.participant_notification:
class: WH\TrainingBundle\Notification\Notification
calls:
- [setMailer, ["#mailer"]]
- [setTemplating, ["#templating"]]
and add to your Notification class the setter methods
class Notification
{
private $mailer;
private $templating;
public function setMailer(\Mailer $mailer)
{
$this->mailer = $mailer;
}
public function setTemplating(\Symfony\Bundle\TwigBundle\TwigEngine $templating)
{
$this->templating= $templating;
}
...your code...

Arguments not being supplied to service

My validator service is not being supplied to validator. I get an error:
"Warning: Missing argument 1 for My\Bundle\Validator\Constraints\MyCustomValidator::__construct()..."
Here is my services.yml
// My\Bundle\Resources\config\services.yml
services:
my.validator.service:
class: My\Bundle\Validator\Constraints\MyCustomValidator
arguments: [ #doctrine.orm.entity_manager ]
Here is my validator class:
// My\Bundle\Validator\Constraints\MyCustomValidator
namespace My\Bundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Doctrine\ORM\EntityManager;
class MyCustomValidator extends ConstraintValidator
{
private $em;
public function __construct($em)
{
$this->em = $em;
}
public function validate($value, Constraint $constraint)
{
// Do something
}
}
Here's my validation.yml
// My\Bundle\Resources\config\validation.yml
My\Bundle\Entity\Page:
properties:
name:
- NotBlank: ~
- My\Bundle\Validator\Constraints\MyCustom: ~
Here's my Constraint class
namespace My\Bundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class MyCustom extends Constraint
{
public $message = 'Something is wrong with "%string%".';
public function validatedBy()
{
return get_class($this) . 'Validator';
}
}
I'd be very appreciative if someone could help me with this.
I've tried changing the argument name to "#doctrine.orm.default_entity_manager" as well, but no luck.
The problem was my services.yml. Because the service is being used as a validator, I MUST use the validator.constraint_validator tag. This is in the documentation. Whoops!
services:
my.validator.service:
class: My\Bundle\Validator\Constraints\MyCustomValidator
arguments: [ #doctrine.orm.entity_manager ]
tags:
- { name: validator.constraint_validator, alias: my_custom_alias}
I also need to override the Constraint classes validatedBy() method so that it returns the alias above, e.g.:
// My\Bundle\Validator\Constrains\MyCustom.php
public function validatedBy()
{
return 'my_custom_alias';
}
You should be passing the doctrine registry to your service and using that to retrieve an entity manager instance.
Change your constructor to this:
public function __construct(RegistryInterface $registry)
{
$this->registry = $registry;
}
Then amend your service configuration to this:
// My\Bundle\Resources\config\services.yml
services:
my.validator.service:
class: My\Bundle\Validator\Constraints\MyCustomValidator
arguments: [ #doctrine ]
Now anytime you need to get an entity manager in MyCustomValidator you can do this
$em = $this->registry->getManager()

Resources