Embedding Controller using Twig - symfony

I've made it simpler (apologies for the complicated question which I've left at the bottom).
I want to have a twig template render another controller as a sub-part.
Here's the parent twig (Resources/views/Default/testRenderParent.html.twig):
<p>Look! I am your father!</p>
<p>But look, I am not your
{{ render(controller("SarelTestBundle:Default:testRenderChild")) }}</p>
Here's the child twig (Resources/views/Default/testRenderChild.html.twig):
KID
The controller (Controller/DefaultController.php):
<?php
namespace Sarel\Test\TestBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* #Template()
**/
class DefaultController extends Controller
{
/**
* #Route("/testRenderParent")
*/
public function testRenderParentAction()
{
return array();
}
/**
* #Route("/testRenderChild")
*/
public function testRenderChildAction() {
return array();
}
}
When you run this, with the following URL /testRenderChild you should get "KID" echoed.
When you run this, with the URL /testRenderParent (notice the "Parent" part), you should get echoed:
Look! I am your father!
But look, I am your KID
However, instead you get this echoed, and the error below it:
Look! I am your father!
But look, I am not your
Look! I am your father!
But look, I am not your
Look! I am your father!
But look, I am not your
Look! I am your father!
But look, I am not your
Look! I am your father!
But look, I am not your
Look! I am your father!
But look, I am not your
Look! I am your father!
But look, I am not your
Look! I am your father!
But look, I am not your
FatalErrorException: Error: Maximum function nesting level of '250' reached, aborting! in /Users/sarel/www/playpen/app/cache/dev/classes.php line 6134
Thus you can see, it's rendering "Look! I am your father! But look, I am your "... and then instead of rendering the child controller with it's twig template, it re-renders the parent, ending in an endless loop, which is arrested by the exception.
--- copy before I changed it ---
--- copy before I changed it ---
--- copy before I changed it ---
So I'm trying to embed a controller (and ultimately the template that the action renders) in my symfony2 project using the guidelines in enter link description here.
My Controller is defined below:
<?php
namespace OTS\CoreBundle\Controller;
/**
* #Template()
*/
class EmergencyContactsController extends Controller
{
public function addEmergencyContactAction(Request $request, $id) {
return array();
}
}
I have a twig template attached to that, which right now just have "hello world" in the form of a twig file called addEmergencyContact.html.twig.
When I go to the URL for this, which is something like localhost/my_route_to_it it works perfectly fine and I see "hello world" on the screen.
Now, according to this I merely have to put the following twig command in:
{{ render(controller('OTSCoreBundle:EmergencyContacts:addEmergencyContact', {'id': 15})) }}
When I do this and I load the holding-route it appears to be trying to load the main route, and then load the main route again inside the spot where I render the child-route. I know this because it's telling me there's variables missing - variables that I'm only using in the main twig file.
Yet, when I:
Don't have the {{ render... }} in there, it works fine - i.e. it's not other code that's giving me the exception of missing variable
Render only the child-route I get a nice "hello world" back - i.e. it's not the child twig or child controller that's buggy

Hmmm. The question is a bit confusing but:
A. Like all controller actions, your addEmergencyContact needs to return a response, not an array. So:
class EmergencyContactsController extends Controller
{
public function addEmergencyContactAction(Request $request, $id)
{
$tplData = array();
$tplData['id'] = $id;
return $this->render('CoreBundle::emergency_contact.html.twig', $tplData);
}
}
B. You need two template files. Something like:
class HelloWorldController extends Controller
{
public function helloWorldAction(Request $request, $id)
{
$tplData = array();
$tplData['id'] = $id;
return $this->render('CoreBundle::hello_world.html.twig', $tplData);
}
}
C. The render line then goes in the hello_world.html.twig file.
{{ render(controller('OTSCoreBundle:EmergencyContacts:addEmergencyContact', {'id': 15})) }}
D. Note that your emergency_contact template will only have access to values passed into it by the emergency contact action. It will not "inherit" the values that hello_world has.
So why is my code formatting messed up? Did not like numbered lists.

This turns out to be a bug in the way annotations work. When I change the controller from being globally #Template() enabled to moving that old-school back to each action individually, it works. Here's the updated controller, look at where the #Template() now sits:
<?php
namespace Sarel\Test\TestBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class DefaultController extends Controller
{
/**
* #Route("/testRenderParent")
* #Template()
*/
public function testRenderParentAction()
{
return array();
}
/**
* #Route("/testRenderChild")
* #Template()
*/
public function testRenderChildAction() {
return array();
}
}

Related

Symfony EventSubscriber: The Page isn't redirecting properly

I have been recently researching EventListeners for the kernal for Symfony4 and I thought I had grasped the basic concept of it but I seem to get a page isn't redirecting properly issue with my EventSubscriber.
Essentially I'd like to do the following logic:
if file_exists $file
redirect to file
else
carry on as normal
Which is how I originally came to kernel.response. Here is my current code:
<?php
namespace App\EventSubscriber;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class MaintenanceSubscriber implements EventSubscriberInterface
{
public function onKernelResponse(FilterResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
if (file_exists('maintenance.flag')) {
$response = new RedirectResponse('maintenance');
$event->setResponse($response);
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => 'onKernelResponse'
);
}
}
this does my logic more or less perfectly, when maintenance.flag doesn't exist it carries on the project as expected, but when I touch maintenance.flag it gets the infamous Firefox page of Page isn't redirecting properly.
I'm not sure if I'm missing something?
I've set up my route:
maintenance:
path: /maintenance
controller: App\Controller\Maintenance\FlagController::flag
which is just a render function - I have a feeling that this could be causing the issue (an endless loop of redirect to flag() which then performs the before action?) but I'm not sure how to render my template from the setResponse() method
Even with the routing conf commented out, I still get the error. So not 100% sure anymore that it's the flag() endless loop theory
I was right indeed about the endless loop being the issue, adding this conditional to exclude the /maintenance url got it to work:
if (strpos($event->getRequest()->getRequestUri(), 'maintenance') !== false) {
return;
}

How to set form action in Symfony2? (2.8 LTS)

So I've been playing around with Symfony forms and I want to change the form action.
I've followed this guide but I don't understand what it means by "target_route". As such, I was getting an error message (see below)
I have the code below and I'm pretty sure the route I used in setAction is valid since I can browse it using my browser.
Any ideas? Thank you
my code:
<?php
// src/AppBundle/Controller/DirectoryController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class DirectoryController extends Controller {
/**
* #Route("/directory/form")
*/
public function formAction() {
$form = $this->get("form.factory")
->createNamedBuilder("form", "form")
->setAction($this->generateUrl("/directory/search"))
->setMethod("get")
->add("search", "submit", array("label" => "Search"))
->add("reset", "reset", array("label" => "Reset"))
->getForm();
return $this->render(
"directory/form.html.twig",
array("form" => $form->createView()
,
)
);
}
/**
* #Route("/directory/search")
*/
public function searchAction() {
return $this->render(
"directory/view.html.twig"
);
}
}
error message:
Unable to generate a URL for the named route "/directory/search" as such route does not exist.
In the example, target_route is the name of a route, not its url. For example, you might define an action like this:
/**
* #Route("/directory/search", name="directory_search")
*/
public function searchAction() {
In that case, your route would have a name of directory_search. You would then use $this->generateUrl('directory_search') to have the router turn the name into a url.
The reason you do it this way (as opposed to using urls directly) is that this allows you to change a url without having to change every place in your code that references it.
->setAction($this->generateUrl("/directory/search"))
setAction() expects a url. So you while you can give it '/directory/search', best practice would be to it $this->generateUrl('directory_search').

Symfony2: phpinfo() using a twig template for layout?

Twig won't process PHP tags. Hence, it is a challenge to create a phpinfo() page based on a layout (say base.html.twig).
Is it possible to dump the HTML content of phpinfo() into some variable and pass it as body content to the layout? Or, is there a better way to proceed?
Just capture the output of phpinfo() with output buffering, and pass it to the template.
ob_start();
phpinfo();
$phpinfo = ob_get_clean();
echo $twig->render('phpinfo.html.twig', array('phpinfo' => $phpinfo));
This is an addition to answer from Federkun.
In controller:
ob_start();
phpinfo();
$phpinfo = ob_get_contents();
ob_end_clean();
return $this->render('phpinfo.html.twig', array(
'phpinfo'=>$phpinfo,
));
Don't forget to put a | raw in twig!
{{ phpinfo | raw }}
class DefaultController extends Controller
{
/**
* #Route("/", name="index")
* #Method("GET")
*/
public function index()
{
ob_start();
phpinfo();
$phpinfo = ob_get_clean();
return new Response(
'<html><body>'.$phpinfo.'</body></html>'
);
}
}
With Symfony, if you just need to check phpinfo()'s output, you can use dump(phpinfo()) inside a controller and you'll get the basic phpinfo()'s output. Also working to call phpinfo() as parameter of the Response object then return it:
class DefaultController extends AbstractController
{
/**
* #Route("/", name="default")
*/
public function index()
{
dump(phpinfo());
// Then return something else, or call directly phpinfo() on return, like bellow
return new Response(phpinfo());
}
}

Why symfony can't find template it rendered in other function

I have a function in my controller like this:
<?php
namespace GradeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use GradeBundle\Entity\User;
use GradeBundle\Entity\SchoolClass;
class MainController extends Controller
{
/**
* #Route("/", name="index_page")
*/
public function index()
{
return $this->render('GradeBundle:Default:index.html.twig');
}
It renders the twig template correctly. However when I use other function:
/**
* #Route("/adminNew", name="add_admin")
*/
public function addAdmin()
{
$session = new Session();
if($session->get('loggedIn') == null)
return $this->redirect($this->generateUrl('index_page'));
else
return $this->render('GradeBundle:Add:newAdmin.html.twig');
}
I have the following error:
Unable to find template "GradeBundle:Default:index.twig.html".
Does anybody have any idea what might be wrong?
It's a typo somewhere you call template:
GradeBundle:Default:index.twig.html
But you have only GradeBundle:Default:index.html.twig template.
Note the difference: html.twig twig.html
I suspect that you extend it in GradeBundle:Add:newAdmin.html.twig by:
{% extends 'GradeBundle:Default:index.twig.html' %}
but should be:
{% extends 'GradeBundle:Default:index.html.twig' %}
Have you made sure to use the correct namespace for the Controller you're using? And are you including the correct files? Also I'm not sure I understand the question correctly - are you saying if you add another function with a different twig file render, the first one no longer renders? Could I see your class names and the namespaces / use statements?
Usually in these instances, it's that the templates are in the wrong place or the correct file is not included in order to find it.
Michael

Share a method with all controllers : Best practice

I'm developing a notification system in symfony2 and I need to get the notifications for every page I'm running.
the trivial solution is to copy the content of the function in every controller and call the function from $this.
How can I make the notification function accessible for every controller? I heard that setting a controller as service is bad practice. what's the best practice then ?
If just using it for output in the template then best approach would be to use a custom TwigFunction and then calling that in a base/layout/extended template like so..
TwigExtension
namespace Acme\NotificationBundle\Twig;
use Acme\NotificationBundle\Provider\NotificationProviderInterface;
class AcmeNotificationExtension extends \Twig_Extension
{
protected $container;
protected $notificationProvider;
public function __construct(
ContainerInterface $container,
NotificationProviderInterface $notificationProvider
)
{
$this->notificationProvider = $notificationProvider;
}
public function getFunctions()
{
return array(
new \Twig_SimpleFunction(
'acme_render_notifications',
array($this, 'renderNotifications')
),
);
}
public function renderNotification($template = 'default:template.html.twig')
{
$notifications = $this->notificationsProvider->getCurrentNotifications();
// Or whatever method provides your notifications
return $this->container->get('templating')->render(
$template,
array('notifications' => $notifications)
);
}
public function getName()
{
return 'acme_notification_extension';
}
}
Services
parameters:
acme.twig.notification_extension.class:
Acme\NotificationBundle\Twig\AcmeNotificationExtension
services:
acme.twig.notification_extension:
class: %acme.twig.notification_extension.class%
arguments:
- #service_container
- #acme.provider.notifcation
// Or what ever your notification provider service is named
tags:
- { name: twig.extension }
This way you would be able to call your notifications in any template using acme_render_notifications() (with the default template) or acme_render_notifications('AcmeOtherBundle:Notifications:in_depth.html.twig') (with a different template if needed) and your controller aren't even touched.
If it was put in a parent template in a block like ..
{% block notifications %}
{{ acme_render_notifications() }}
{% endblock notifications %}
..then it would run on every page unless you had overridden the block in your child class.
The way I would do it, and I think it is among the best practices, is setting up a service with the function then just instantiate it in each controller.
No doubt its bad practice,
Many solutions are possible, Here we will discuss on abstract level
A global utility can be used with different scopes (application, session scope) depending upon the requirements
Make this utility accessible to all available controllers

Resources