How to pass variable to controller without routing? - php

class HelloController
{
/**
* #Route("/", name="hello")
*/
public function indexAction($name)
{
return new Response($name);
}
}
I would like pass variable $name to indexAction without use routing.
In documentation I found:
services:
# ...
# explicitly configure the service
AppBundle\Controller\HelloController:
public: true
tags:
# add multiple tags to control
- name: controller.service_arguments
action: indexAction
argument: logger
# pass this specific service id
id: monolog.logger.doctrine
This shows us how to pass another service to the controller, but how to pass a simple variable?

try this inside routing.yml:
defaults:
_controller: AppBundle:Hello:index
name: "WhatYouWantToPass"

Related

Symfony single controller as service

Is it possible to make symfony single controller as service
I am trying to make a single controller as service not the whole bundle
code which i have tried is in service.yml (CMSBundle)
cms.exampleController:
class: Website\CMSBundle\Controller\ExampleController
autowire: true
and trying to inject the service from a bundle which is already service, public and autowrie true
example in controller
namespace Website\CMSBundle\Controller;
use Common\UtilityBundle\Listener\ContactData; (UtilityBundle in this every thing is service)
class ExampleController extends Controller
{
public function testAction(Request $oRequest, ContactData $oContactData)
{
//this will become error because $oContactData is always null
}
}
error message
Controller "Website\CMSBundle\Controller\ExampleController::testAction()" requires that you provide a value for the "$oContactData" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
My routes
example_details:
path: /test
defaults: { _controller: WebsiteCMSBundle:Example:test, eventId:null }

Adding services to a Controller through "container.service_subscriber" not working as expected

I am trying to use the container.service_subscriber tag on my Controller to make some services available without injecting them through the constructor. In our project we don't want to use the autowiring and also can't use the autoconfigure option.
The structure of the Controller is as follow:
I have a base BaseController which extends from the AbstractFOSRestController of FOSRestBundle which has some common used methods for all my Controllers. That service will be used as parent for my other Controllers.
The service definition looks like this:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "#service1"
- "#service2"
- ...
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
#autowire: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
The class looks like this:
/**
* User controller.
*/
class UserController extends AbstractCRUDController implements ClassResourceInterface
{
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
'servicexyz' => ServiceXYZ::class,
]);
}
.......
}
The problem I have is, if I set autowire: false, it always automatically sets the full container and with this the appropriate deprecation message (as I am not setting it myself):
User Deprecated: Auto-injection of the container for "WM\ApiBundle\Controller\UserController" is deprecated since Symfony 4.2. Configure it as a service instead.
When setting autowire: true Symfony does respect the container.service_subscriber tag and only sets the partial container (ServiceLocator), which also would solve the deprecation message. I would have expected that autowiring should not make any differences in this case because I am explicitly telling the service which other services it should have.
Am I using the tags wrong or do I have a general problem in understanding how to subscribe a service to a Controller?
The basic issue is that the builtin service subscriber functionality will only inject the service locator into the constructor. A conventional controller which extends AbstractController uses autoconfigure to basically override this and uses setContainer instead of the constructor.
# ApiBundle/Resources/config/services.yaml
services:
_defaults:
autowire: false
autoconfigure: false
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
class UserController extends AbstractController
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
// ...
'logger' => LoggerInterface::class,
]);
}
public function index()
{
$url = $this->generateUrl('user'); // Works as expected
// $signer = $this->get('uri_signer'); // Fails as expected
$logger = $this->get('logger'); // Works as expected
return new Response('API Index Controller ' . get_class($this->container));
}
}
Results in:
API Index Controller Symfony\Component\DependencyInjection\Argument\ServiceLocator
Indicating that a service locator (as opposed to the global container is being injected).
You can also configure your service to use the setContainer method and eliminate the need for a constructor. Either approach will work.
Api\Controller\UserController:
public: true
tags: ['container.service_subscriber']
calls: [['setContainer', ['#Psr\Container\ContainerInterface']]]
Solution to the problem is to extend the service definition of the Controller with a call to setContainer to inject the '#Psr\Container\ContainerInterface' service:
WM\ApiBundle\Controller\BaseController:
class: WM\ApiBundle\Controller\BaseController
abstract: true
arguments:
- "#service1"
- "#service2"
- ...
calls:
- ['setContainer', ['#Psr\Container\ContainerInterface']]
WM\ApiBundle\Controller\UserController:
parent: WM\ApiBundle\Controller\BaseController
public: true
class: WM\ApiBundle\Controller\UserController
tags:
- { name: 'container.service_subscriber'}
- { name: 'container.service_subscriber', key: 'servicexyz', id: 'servicexyz' }
This will give me a ServiceLocator as container containing only the regiestered services instead of the full container without using the autowire option.
Sidenote: Setting the #service_container would inject the full container.
For completeness, there was already an issue on the symfony project where this was discussed.

Symfony inject EntityManager in SwitchUserListener

My question is about how to inject the entity manager in the SwitchUserListener that already has 9 parameters.
I have a custom switch user flow where I need to set the ExternalClient passed along with the _switch_user parameter (?_switch_user=user1&external_client_id=1) in the session. I first have to fetch the ExternalClient from the database before I can set it.
In parameters.yml I've added
security.authentication.switchuser_listener.class: App\Bundle\Listener\SwitchUserListener
And for the content of App\Bundle\Listener\SwitchUserListener I used the Symfony SwitchUserListener Symfony\Component\Security\Http\Firewall\SwitchUserListener.php
Everything works and when I fetch the external_client_id parameter from the request variable in the listener it is populated. But I can't seem to get access to the entity manager.
Things I've tried:
Add decorator in services.yml
app.decorating_switch_user:
class: App\Bundle\Listener\SwitchUserListener
decorates: security.authentication.switchuser_listener
arguments: ['#app.decorating_switch_user.inner', '#doctrine.orm.entity_manager']
public: false
Overriding parent dependencies in services.yml
security.authentication.switchuser_listener:
abstract: true
test:
class: "%security.authentication.switchuser_listener.class%"
parent: security.authentication.switchuser_listener
public: false
# appends the '#doctrine.orm.entity_manager' argument to the parent
# argument list
arguments: ['#doctrine.orm.entity_manager']
Listen to SwitchUserEvent instead
app.switch_user_listener:
class: App\Bundle\Listener\SwitchUserListener
tags:
- { name: kernel.event_listener, event: security.switch_user, method: onSwitchUser }
Here I've replace the contents of 'App\Bundle\Listener\SwitchUserListener' with:
class SwitchUserListener
{
public function onSwitchUser(SwitchUserEvent $event)
{
$request = $event->getRequest();
echo "<pre>";
dump($externalClientId = $request->get('external_client_id'));
echo "</pre>";
exit;
}
}
I'm getting the external_client_id as well with this attempt but I have no idea how to inject the entity manager. And even If I did, I'd have no way of getting the original user that initiated the _switch_user request. SwitchUserEvent only has access to the getTargetUser() method.
Conclusion:
If anybody has experience with this topic and is willing to share it that would be great. Ideally I would add the entity manager service to the previous 9 arguments of the __construct function. I'm expanding that class just like Matt is doing here: Symfony2: Making impersonating a user more friendly
You can override the service as follows. You may need to look up/change the concrete order of service arguments as it changed between symfony versions. Some arguments like $providerKey can be left empty as they will be changed/injected automatically by symfony.
In order to save some time coding you won't need to override the constructor if you use setter injection.
A look at Symfony's default SwitchUserListener (switch to the tag/version used in your application) will help when implementing your new handle method.
# app/config/services.yml
services:
# [..]
security.authentication.switchuser_listener:
class: 'Your\Namespace\SwitchUserListener'
public: false
abstract: true
arguments:
- '#security.context'
- ~
- '#security.user_checker'
- ~
- '#security.access.decision_manager'
- '#?logger'
- '_switch_user'
- ~
- '#?event_dispatcher'
- ~
calls:
- [ 'setEntityManager', [ '#doctrine.orm.entity_manager' ]]
tags:
- { name: monolog.logger, channel: security }
Now your SwitchUserListener might look like this:
namespace Your\Namespace;
use Symfony\Component\Security\Http\Firewall\SwitchUserListener as DefaultListener;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class SwitchUserListener extends DefaultListener
{
/** #var EntityManagerInterface */
protected $em;
public function setEntityManager(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* Handles the switch to another user.
*
* #throws \LogicException if switching to a user failed
*/
public function handle(GetResponseEvent $event)
{
// Do your custom switching logic here
}
}
Don't forget to clear your cache!

Symfony2 won't pass arguments to a service

I am trying to pass arguments to my new Controller in new Bundle I have created via cli ( I have tried to do it manually too ). It can be anything, string, service, parameter from parameters.yml file, nothing comes through.
Error:
{"code":500,"message":"Warning: Missing argument 1 for MyProject\\PosBundle\\Controller\\OfferController::__construct(), called in \/var\/www\/vhosts\/httpdocs\/myproject\/vendor\/symfony\/symfony\/src\/Symfony\/Component\/HttpKernel\/Controller\/ControllerResolver.php on line 162 and defined","errors":null}
My Files are
service.yml
services:
myproject_pos_offer_controller:
class: MyProject\PosBundle\Controller\OfferController
arguments: ['templating']
I have tried to do this:
services:
myproject_pos_offer_controller:
class: MyProject\PosBundle\Controller\OfferController
arguments:
someString: 'templating'
OfferController:
class OfferController extends RestController
{
private $someString;
public function __construct($someString)
{
$this->$someString = $someString;
}
public function indexAction(){
}
}
What am I doing wrong or what did I forget to do?
And Cerad was right (Thanks for help!). I had to pass my controller in my routing.yml configuration as a service. Do find that I had to debug ControllerResolver.php, that was fun.
Solution
Code above is correct. The problem was lying in my routing.yml
Wrong
myproject_pos.offer_create:
path: /{store_hash}/offers
defaults: { _controller: MyProjectPosBundle:Offer:create }
methods: 'POST'
Correct
myproject_pos.offer_create:
path: /{store_hash}/offers
defaults: { _controller: myproject_pos_offer_controller:createAction }
methods: 'POST'
As you see the key is in defaults attrubute.

Check routing in symfony2

I want to learn symfony and I start to create a small application. I have a question related to the routes. So, I have in my project the routes :
/admin/homepage, /admin/news, admin/galery
No if I write in url /admin, this route doesn't exist and I get as error No route found for "GET /admin/". Exist a way to check if route doesn't exist and redirect to another route for example ? Thx in advance and sorry for my english
My routes :
news_all:
path: /news/all/{page}
defaults: { _controller: AppAdminBundle:News:all, page: 1 }
requirements:
page: \d+
_method: GET|POST
news_add:
path: /news/add
defaults: { _controller: AppAdminBundle:News:add }
In your case the best solution would be to override default ExceptionController and add custom logic there, e.g. redirection to other page - according to the docs: http://symfony.com/doc/current/cookbook/controller/error_pages.html#overriding-the-default-exceptioncontroller
# app/config/config.yml
twig:
exception_controller: AppBundle:Exception:showException
Note:
Instead of creating a new exception controller from scratch you can, of course, also extend the default ExceptionController. In that case, you might want to override one or both of the showAction() and findTemplate() methods. The latter one locates the template to be used.
Symfony's good practise are to set rout in your controller, using annotations
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
and
/**
* #Route("/news/add", name="news_add")
*/
public function addAction()
{
// ...
}
Also, with annotation, you can set rout for a whole controller.
Which is, in your case, what you're looking for.
/**
* #Route("/admin")
*/
class NewsController extends Controller
{
/**
* #Route("/news/add", name="news_add")
*/
public function addAction()
{
// ...
}
}
Also, I advice you to take a look at #Template annotation.
Else, to answer your question, I think you can make a custom twig function (check this link for more information). Function that checks is the given name a valid route:
function routeExists($name)
{
// I assume that you have a link to the container in your twig extension class
$router = $this->container->get('router');
return (null === $router->getRouteCollection()->get($name)) ? false : true;
}

Categories