Symfony2 Set Controller in the kernelControllerEvent using bundle:controller:action notation - symfony

I am trying to do something like the following question:
Trying to swap a controller using an event listener with Symfony2
However, when I use the code (as recommended in the answer):
$event->setController('MyMainBundle:Manage:show');
I just get an error:
LogicException: The controller must be a callable (MyMainBundle:Manage:show given).
Is there a way to use that Bundle:Controller:Method syntax in setController? Or maybe some other method I can call to resolve that to a "callable"?

What you should give to $event->setController is a callable.
What you give a string representing the logical path to a callable.
You can resolve this string using symfony's ControllerResolver.
You have to inject the controller_resolver service in your listener, and then use it like this:
$request = new Symfony\Component\HttpFoundation\Request();
$request->attributes->set('_controller', 'MyMainBundle:Manage:show'));
$event->setController($this->resolver->getController($request));
But you are clearly doing the framework's job here.

Related

How to use Symfony container parameters in annotations?

Is it generally possible to use container parameters in method/class annotations in Symfony 5.3+?
For example one of my action methods uses a #ParamConverter annotation using the FOSRest RequestBodyParamConverter:
class SomeController extends AbstractController {
/**
* #ParamConverter("post", converter="fos_rest.request_body")
*/
public function sync(Request $request, Post $post): Response {
...
}
}
Instead of explicitly specifying the the converter here, I would like to use a container parameter instead. However, this does not seem to work.
I have checked that the parameter is defined correctly:
$ php bin/console debug:container --parameters
...
my.post.converter fos_rest.request_body
But I cannot use it:
/**
* #ParamConverter("post", converter="%my.post.converter%")
*/
No converter named '%my.post.converter%' found for conversion of
parameter 'post'.
I found other questions which indicate, that using parameters should be possible. At least when working with other annotations like #Route.
Is this a limitation of the #ParamConverter annotation (= a special feature of the #Route annotation), or is there anything wrong with my setup?
I know that there are other ways of specifying a different converter in this example. However, this is just an example and the question is, whether using a parameter here should be possible or if I made some error.
Not all annotations are equal. Each has its own processor, and each annotation argument has different accepted inputs.
And the annotations simply enable some underlying component, so sometimes (probably most of the time) if you can use a container parameter in an annotation, it's because the underlying component supports using a container parameter for that argument (as is the case with the Routing component, that can be configured via other ways than annotations/attributes)
Using a container parameter on the question provided example would not be particularly useful, if at all.
That argument expects a service ID, and since those IDs are defined on the container configuration (the same place where you would configure the parameters), moving the definition to a parameter would gain one nothing.
If you want to have it "configurable", maybe use a custom service id that you could define as an alias to the real service, that way you can have multiple converters and alias the correct one via configuration.
Not convinced that that would be something useful in real life, but your application constraints might be altogether different.

Fix circular reference in symfony when using SerializerInterface

I'm getting a circular reference error when serializing a component. Usually this can be fixed using
$normalizer->setCircularReferenceHandler()
However, I'm using the SerializerInterface like this:
/**
* #Route("/get/{id}", name="get_order_by_id", methods="GET")
*/
public function getOrderById(SerializerInterface $serializer, OrderRepository $orderRepository, $id): Response
{
return new Response($serializer->serialize(
$orderRepository->find($id),
'json',
array('groups' => array('default')))
);
}
Is it possible to fix a circular reference error when serializing using this interface?
You totally can. Just add this in your framework config.
framework:
serializer:
circular_reference_handler: App\Serializer\MyCustomCircularReferenceHandler
This handler will work globally. Make sure you register it as a service. I does not need to implement any interface. So just a class with an __invoke() will suffice. That invoke will receive the object that is being "circle referenced" as the only argument.
You can either return the id or do some really cool stuff, like creating a uri for the resource. But the implementation details are totally up to you, as long as you don't return the same object, everything will be fine.
:)
According to the Symfony API Reference on the interface there doesn't look to be a way to execute that function or retrieve the normalizer.
Even in the Serializer, there doesn't look to be a way to retrieve the normalizer after creating the serializer.
You're best off creating the normalizer before the serializer to achieve this, rather than injecting the interface via config files. (Relevant docs link)

Symfony2 use Doctrine in ExceptionController

Hi I'm trying to use Doctrine inside the default ExeptionController but I get the following error:
Fatal error: Call to undefined method Symfony\Bundle\TwigBundle\Controller\ExceptionController::getDoctrine()
when I try to call:
$manager = $this->getDoctrine()->getManager();
What I'm trying to do is to have a custom 404 page where I can present some items from the database.
Could you please help me? Thank you!
You may also inject the Doctrine service as a dependency in your Controller (in that case you don't need to entend class Controller)
You will have to create your own ExceptionController extending the default one. You'll have to declare it as described here: http://symfony.com/doc/current/cookbook/controller/error_pages.html#custom-exception-controller. Your custom controller must have a constructor with at least an argument of type Registry (Doctrine). You have to declare that controller as a service in your service.yml (or xml depending on your config) Have a look at the symfony doc for further explanation on how to do that. For the moment I can't help you much more as I'm outside with my Android and it's rather difficult to make long answers

How to pass arguments to controller from route in Symfony2

I'm working on a Symfony2 project and am trying to figure out how to pass parameters from the route configuration to the controller. I know I can configure default values in the route configuration, and retrieve the values in the controller using the appropriate var name in the function declaration, but that isn't exactly what I want.
My use case is the following. I have a standard method in my controller that I want to access from 2 or 3 different routes. Depending on which route is being called, I want to "configure" the method differently. I can accomplish this in a few ways:
In my controller, check the route name using `$this->container->get("request")->get("_route"), but that is ugly, and then I am hardcoded to the route name. Moves configuration to the controller, which should just be logic - not configuration.
Create a base controller class, and subclass each method for my different routes. Each subclassed method would then have the necessary configuration within the method. Cleaner soln than #1, but still "heavy" in the sense of having multiple classes for a simple need and still pushes configuration data into the business logic.
Put configuration data into the route configuration. In the controller, access the configuration data as required. Ideal solution, but don't know how.
I can use the route default array to specify my arguments, but then must make sure to use a regex to ensure that the params are not overridden at the URL level (security risk). This is functional, but still kinda cludgy and not a pretty hack.
I presume that there must a better way to do this, but I can't seem to figure it out. Is there a way to access the routing object from the controller, and access the different configuration parameters?
You can pull the actual route from the router service. Something like:
$routeName = $this->container->get("request")->get("_route");
$router = $this->container->get("router");
$route = $router->getRouteCollection()->get($routeName);
Not sure if this would be such a great design though. Consider passing a $configName to your controller method, adding a parameter with the same name in a config file then using getParameter to access it. That would eliminate the route stuff from the equation.
Something like:
zayso_arbiter_import:
pattern: /import
defaults: { _controller: ZaysoArbiterBundle:Import:index, configName: 'someConfigName' }
public function importAction(Request $request, $configName)

using another bundle in Symfony 2, can't access $this->get

I have a REST API built with Symfony2 and the FOSRestBundle. This all works fine, however in one service I combine data form another service in a different bundle - this seemed like it would be simple but it is not.
I am creating a new request object and adding in my parameters, from there I fire off the request to the other service, the service receives the request fine, however, when I try to use $this->get it gives me the good old Call to a member function get() on a non-object in ...
I know that I am missing the service container (I don't entirely understand why it's available when I call a hit the first bundle but not the second), that's all well and fine but how do I inject it or a component of it so I can use $this->get to hit my custom services defined in services.yml? (easy to pass them the service container using arguments:
container: "#service_container")
Setting this bundle up as a service won't work as FOSRestBundle does not call it as a service.
In Short: I want to be able to get data from bundle2 when inside bundle1 by doing
namespace MyVendor\Bundle1\Controller
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use MyVendor\Bundle2\Controller\Bundle2ClassName;
class Bundle1 {
//if i wanted to do this here it would work fine:
// $this->get('my.service.defined.in.service.yml');
$bundle2 = new Bundle2ClassName();
$returned_data = $bundle2->myFunction();
}
Then once inside myFunction in bundle2 if I try to call the exact same service function i get the dreaded get error. If I call bundle2 directly through the FOSRest route i obviously don't have that problem.
namespace MyVendor\Bundle2\Controller
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class Bundle2 {
//this does not work
$this->get('my.service.defined.in.service.yml');
//do some stuff then return response
return($response);
}
I've read all the service container docs over and over again, so if you're going to link to them i'd appreciate it if you could point out the exact section where it explains how this stuff is handled. This has been the one problem i've never been able to fully understand since I started working with Symfony a few months back.
P.S. could someone with enough points add the FOSRestBundle as a tag?
Thanks!
First of all you should use $this->forward to forward request to another controller.
Second, the reason you don't have access to service container in second controller is probably because you're trying to manually initialize it - never do that unless you absolutely know what you're doing (specifically, you forgot to pass service container as controller dependency).
Third, just as an example on how things work - your original controller dependency on service container is handled by the same container and extends ContainerAware, on that controller initialization a setContainer() is called, which you most likely forgot to do when manually initializing second controller. So to get it working (which again I strongly recommend not doing), you should do this:
class Bundle1 {
//if i wanted to do this here it would work fine:
// $this->get('my.service.defined.in.service.yml');
$bundle2 = new Bundle2ClassName();
$bundle2->setContainer($this->container);
$returned_data = $bundle2->myFunction();
}
The reason you're getting the $this->get() on a non-object... error is because $this->get() in your controller is actually a shortcut to $this->container->get() (defined in Symfony\Bundle\FrameworkBundle\Controller\Controller)

Resources