Symfony2 - passing values between ESI and main request - symfony

I need to pass a value from the main controller in a route to the controller used by an ESI.
So a controller renders a Twig template and in Twig this is called:
{{ render_esi(url('route_name')) }}
The above renders a controller. It's those two controllers I need to pass information between.
I've noticed that using $request->attributes does't work, even though it would if one wasn't an ESI:
//these WON'T pass between master request and ESI
$request->attributes->set('the_value');
$request->attributes->get('the_value');
Sessions aren't ideal as I would need to ensure they were cleared on some occasions.
I really just want the same request to pass some information once. I was hoping that the $request->attributes would be shared as to me it's one request (although I believe Symfony calls it one master request with various sub-requests and I'm guessing $request->attributes are locked to that scope).
Passing it as a query param in the ESI call isn't good either as it can sometimes be an array of info that needs to be passed.
Any ideas?

You can pass arguments to your action like this:
{{ render_esi(controller('YourBundle:Default:news', { 'max': 5 })) }}
or use route parameters like this
{{ render_esi(url('latest_news', { 'max': 5 })) }}
as correctly answered in this question.

Related

Parameter injection for symfony render_esi (listener instead of static in template)

I'm aware that one can add parameters for the esi request like {{ render_esi('bundle:controller:action', {foo: 'bar'}) (would add the parameter foo with the value bar to the fragment uri).
My task requires setting this parameter dynamically from outside the template (at the moment the uri is generated).
I could not find an event that would be called as the uri is generated. Google did also not help solving this for me.
Basically what I want is to call {{ render_esi('bundle:controller:action') }} and it to result in an esi fragment uri containing the parameter foo=bar (added using some service?).
Is there a nice solution for this issue (without overwriting the fragment generators used by symfony that is)?
EDIT:
The parameter is not used by any of the controllers and is more or less required on a global scale as a request-listener will check the parameter and adjust a service with additional information.
The listener will remove the parameter afterwards.

A substitute for render(controller())

actually in my project I need to display in the navbar if the user have requests. To do this I use in the twig template a:
{{ render(controller("AppBundle:TeamRequest:numberRequests")) }}
The controller is very simple, just do a doctrine request, and generate a view.
But this part of code is slow... It take 200ms more than if I disable it.
Is there a better way to do this ?
PS: Someone know what is the Symfony\Component\HttpKernel\EventListerner\ProfilerListener ? Because it's this part that take a lot of time in the main and subrequest.
Thanks a lot :-)
Thanks to the answers, the idea is to do a service. In my case, I don't change it, because as you can see in graph, the main part of the time is for the debugbar, finally, it takes few ms to generate it.
All the Timeline (Threshold: 50ms)
Only the subrequest Timeline (Threshold: 1ms)
Try to move this function into a service. Then register this service in twig as a global variable. config.yml:
# Twig Configuration
twig:
globals:
number_requests_service: %your_service_name%
Then in twig you can load it:
{{ number_request_service.renderSomething }}
If you need the user request in the service itself, then give them the request_stack in service configuration.
I had the same problem few day ago, and i solve it with a twig extension you can create it like a service and inject your entityManager to do your request and return a view. With this way Symfony2 isn't load a second time.

Include a controller to get parameter in Twig

I would like to include a controller into my defaut layout (in app/) to get access to the parameters returned in the array. I don't want to render. I tried to use {{ render_hinclude(controller('L3O1ProjetBundle:Advert:index')) }}
But I'm not sure I understand what it does...
Thanks for your help!
Here is an example from the documentation.
{# app/Resources/views/base.html.twig #}
{# ... #}
<div id="sidebar">
{{ render(controller(
'AcmeArticleBundle:Article:recentArticles',
{ 'max': 3 }
)) }}
</div>
That calls AcmeArticleBundle:Article:recentArticles so first the bundle then the controller and then the function and render the output from that function.
http://symfony.com/doc/current/book/templating.html
It is little bit strange approach to get parameters from the controller without render. But if you really need to get some set of data inside Twig, then the best way will be create a twig extension that will return an array. And if you need same data be returned as the controller makes, better to rebuild the app's architecture: dedicate the controller logic to a service that will return needed values both controller/extension, inject the service to the controller and twig extension and returns data from the service.

Symfony 2 - missing mandatory parameters exception on twig extends

I'm currently getting a very wired twig exception:
If I use the twig {% extends 'some:template' %}, I get the following twig exception:
An exception has been thrown during the rendering of a template ("The "_projectView" route has some missing mandatory parameters ("id").") in "xy:Project:view.html.twig".
But if I remove {% extends 'some:template' %}, the template is displayed correctly - this for my part rules out any problems with routing or the controller, it has to be a problem with the template but I can't figure it out. No variables are used in the parent templates.
Inside the template you're trying to extend you're trying to generate a url from a route '_projectView' but you don't provide all necessary parameters.
Either add a default id to your route ...
route_name:
pattern: /whatever/{id}
defaults: { id: 1 }
... or do something like this in your template:
{{ path('route', { 'id' : entity.id|default('1') }) }}
Quite simply, to display correctly, each twig needs to have all the variables it needs defined. They are either in the URL or passed into the render function. If any of these variables are missing, then Symfony will throw this error.
Given what you've said it seems to me that the twig you are extending has a variable in it that it wants. In all other places you have extended this twig, you have provided this variable already either in the URL or in one of the parent twigs and so it has not complained. In this case you try to use it and that variable is missing. So it complains.
Check to make sure your route passes in the right variable or that your controller passes the right variable into the render function.

Symfony2 - Issue with writing tests for a controller that is being passed an object as a param

I'm running into an issue with writing a test for a controller inside a Symonfy2 bundle.
The controller is only ever accessed via Twig's {% render %}, and works great in the application, but the test keep failing, no matter what way I approach it. Here is basically what's going on:
Controller A. - The "parent" controller. Loads via a typical URL.
Controller B. Loaded via {% render %} from a template rendered by Controller A. An object variable is passed to Controller B in the {% render %} tag.
Controller C. Loaded via {% render %} from a template rendered by Controller B.
This all works great in the actual application, and having things structured this way allows for the maximum amount of code reuse. The issue comes in when I started writing a unit test for Controller B. Because this is an internal only controller, I thought it would be best call the methods for testing like this:
$this->obj = new ControllerB();
$this->obj->setContainer($this->getContainer());
$this->obj->methodToTest();
But this leads me to this error message:
Twig_Error_Runtime: An exception has been thrown during the rendering of a template ("You cannot create a service ("request") of an inactive scope ("request").") at line XX - This is the line where we are attempting to render a method from Controller C via {% render %}
So it seems that because the call to the method in Controller B was not part of a request, it can't make an internal sub-request to controller C. The solution to that would seem make a request to controller B instead via:
$this->client = static::createClient(...);
$crawler = $this->client->request('GET', '/routeToControllerB');
But this won't work either because I need to pass objects and arrays to the method in Controller B, and they are too big to serialize/json_encode and fit in the URL to request.
I've experimented to try and force a request into the objects container:
$this->client = static::createClient();
$this->client->followRedirects();
$container = $this->getContainer();
$container->get('session')->start();
$request = Request::createFromGlobals();
$container->set('request', $request);
$user = new \User(1111);
\Auth::set_user($user);
$user = new \Company\Component\Security\User\User(\Auth::user());
$token = new UsernamePasswordToken($user, 'phpunit', 'PHPSESSID', $user->getRoles());
$container->get('security.context')->setToken($token);
$this->obj = new ControllerB();
$this->obj->setContainer($container);
But so far I can't find anything that will let me test a controller method that needs to be passed an object and also uses {% render %} (an internal sub-request) in the template. Anyone ever run into this before?
I know I'm a little but here my advices:
If you really want to do that, you can make your controller a service, give it all the dependencies it needs, and then write a unit test. See http://php-and-symfony.matthiasnoback.nl/2012/06/symfony2-testing-your-controllers/
But I recommand you not to unit test your controllers. If they are well written, they don't contain any business logic and unit testing them is kind of pointless.
You should go with the functional test of your controller directly if there is an associated route (but in this case you won't be able to give it objects directly), otherwise if the controller is not designed to be accessed directly from a browser (because you give it objects, or just because you don't want to), just test the controller including it and it will cover both controllers. In your example, just test the controller A, and it covers B and C implicitly.

Resources