Symfony2 - Controller as a Service - Routing via annotations - symfony

I am experimenting with creating controllers as services as shown at http://symfony.com/doc/current/cookbook/controller/service.html. I’ve followed this example and everything works fine when I have the route set in app/config/routing.yml. However when I try and set the route via annotations I get an error
My routing.yml file looks like this:
#hello:
# path: /hello/{name}
# defaults: { _controller: app.hello_controller:indexAction }
hello:
resource: "#EventBundle/Controller/HelloController.php"
type: annotation
My controller looks like this:
<?php
namespace Me\EventBundle\Controller;
//use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
//class HelloController extends Controller
class HelloController
{
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
/**
* #Route("/hello/{name}", name="hello")
*
*/
public function indexAction($name)
{
return $this->templating->renderResponse(
'EventBundle:Default:test.html.twig',
array('name' => $name)
);
}
}
As I say if I just use routing.yml and not the annotations the page renders correctly. However using annotations I get the error:
Catchable Fatal Error: Argument 1 passed to Me\EventBundle\Controller\HelloController::__construct() must be an instance of Symfony\Bundle\FrameworkBundle\Templating\EngineInterface, none given, called in /Library/WebServer/Documents/symfony-project/app/cache/dev/classes.php on line 2176 and defined
EDIT - as requested in comments:
service.yml looks like:
services:
app.hello_controller:
class: Me\EventBundle\Controller\HelloController
arguments: ['#templating']

The answer with thanks especially to #Artamiel and #Cerad was to add #Route(service="app.hello_controller") just above my class name, so it now looks like:
/**
* #Route(service="app.hello_controller")
*/
class HelloController
{
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
..........etc

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'

Symfony 3 inject service into controller

I'm using symfony 3.1.7 and I have problems with inject services into a controller, my english is bad I believe that it is better if I show all the code.
The error:
Type error: Argument 1 passed to
...Bundle\Controller\AdminController::__construct() must
implement interface
Symfony\Component\DependencyInjection\ContainerInterface, none given,
called in /......./project/var/cache/dev/classes.php on line 2512
This is my code:
GenericRestController
namespace MyCoreBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use MyCoreBundle\Handler\GenericRestHandlerInterface as Generic;
/** other uses like FOSRestController **/
/**
* Class GenericRestController
*/
abstract class GenericRestController extends FOSRestController
{
protected $handler;
protected $container;
public function __construct(Container $container,Generic $handler)
{
$this->container = $container;
$this->handler = $handler;
}
/** other methods like get, post, put, etc **/
}
AdminController
namespace ...Bundle\Controller;
use MyCoreBundle\Controller\GenericRestController;
class AdminController extends GenericRestController
{
}
RESOLVED
The solution was change the routes as Cerad says.
Oficial documentation
services.yml
services:
app.hello_controller:
class: AppBundle\Controller\HelloController
routing.yml
hello:
path: /hello
defaults: { _controller: app.hello_controller:indexAction }
Thanks mtaqi and Cerad
The solution was change the routes as Cerad says.
Oficial documentation
services.yml
services:
app.hello_controller:
class: AppBundle\Controller\HelloController
routing.yml
hello:
path: /hello
defaults: { _controller: app.hello_controller:indexAction }
Sorry for the delay and thanks!

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.

Monolog in Symfony from non-controller Class

I have the following class:
namespace AppBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
class UserRegistrationListener implements EventSubscriberInterface
{
protected $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
);
}
/**
* take action when registration is initialized
* set the username to a unique id
* #param \FOS\UserBundle\Event\FormEvent $event
*/
public function onRegistrationInit(UserEvent $userevent)
{
$this->logger->info("Log Something");
$user = $userevent->getUser();
$user->setUsername(uniqid());
}
}
and I have been trying for hours to log something with monolog from within it but have had no luck.
I have read much of the documentation and I believe I need to somehow 'Inject' monolog as a service. What I have read however does not seem to be clear to me.
Some details:
#config_dev.yml
monolog:
channels: [chris]
handlers:
mylog:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%_chris.log"
channels: chris
formatter: monolog.my_line_formatter
.
#services.yml
services:
monolog.my_line_formatter:
class: Monolog\Formatter\LineFormatter
arguments: [~, ~, true]
app.user_registration:
class: AppBundle\EventListener\UserRegistrationListener
arguments: [#logger] ## changed to [#monolog.logger.chris] to us custom channel
tags:
- { name: kernel.event_subscriber }
What do I have to do to get Monolog working with my formatter inside this class?
UPDATE:
#griotteau I have done what you have posted in your answer but I still get an error:
CRITICAL - Uncaught PHP Exception Symfony\Component\Debug\Exception\ContextErrorException: "Warning: Missing argument 1 for AppBundle\EventListener\UserRegistrationListener::__construct(), called in ...filepath...\app\cache\dev\appDevDebugProjectContainer.php on line 384 and defined" at ...filepath...\src\AppBundle\EventListener\UserRegistrationListener.php line 18
SOLVED ERROR I already had a service with the same class (not shown in ym question). #griotteau 's answer is correct.
You can pass arguments when you declare your service
In services.yml :
app.user_registration:
class: AppBundle\EventListener\UserRegistrationListener
arguments: [#logger]
tags:
- { name: kernel.event_subscriber }
In your class, add a constructor :
protected $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
So when you want to add a log :
$this->logger->info(...);

How to use a controller as service with #Route annotation in Symfony 2?

I've a controller which is configuered as a service. I'd like to use #Route annotations to define the route.
When I try to access the route I get:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Galexis\RequestDispatcherBundle\Controller\RequestDispatcherController::__construct() must implement interface Psr\Log\LoggerInterface, none given, called in /Users/ugxnbpluse/development/git/integrationPHP/symfony2/app/cache/dev/classes.php on line 2282 and defined in /Users/ugxnbpluse/development/git/integrationPHP/symfony2/src/Galexis/RequestDispatcherBundle/Controller/RequestDispatcherController.php line 29
From the error message I understand, that symfony doesn't know, that it should not call new for the controller but take it from the DI container.
The symfony docs (last section) tell me to add something like
#Route(service="my_post_controller_service")
but it seams that symfony does not really support the service property:
BadMethodCallException: Unknown property 'service' on annotation 'Symfony\Component\Routing\Annotation\Route'.
Any ideas?
Controller:
class RequestDispatcherController
{
// ...
public function __construct(
LoggerInterface $logger,
RequestDispatcherService $requestDispatcherService)
{
$this->logger = $logger;
$this->requestDispatcherService = $requestDispatcherService;
}
/**
* #Route("requestDispatcher/{applicationName}")
* #return Response
*/
public function dispatch(Request $request)
{
// ...
}
}
routing.yml:
_request_dispatcher:
resource: "#FooRequestDispatcherBundle/Controller/RequestDispatcherController.php"
type: annotation
service.yml:
parameters:
request_dispatcher.class: Foo\RequestDispatcherBundle\Service\RequestDispatcherService
request_dispatcher_controller.class: Foo\RequestDispatcherBundle\Controller\RequestDispatcherController
services:
request_dispatcher_service:
class: "%request_dispatcher.class%"
arguments: ["#foo.integration.application", "#buzz.multi.client" ]
request_dispatcher_controller:
class: "%request_dispatcher_controller.class%"
arguments: ["#logger", "#request_dispatcher_service"]
I think you should use use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; instead of use Symfony\Component\Routing\Annotation\Route;.

Resources