How to add some event to twig in symfony 2.1 - symfony

I want add my control code before rendering template.
Example:
Have templates:
user.html.twig
Controller:
return $this->render('....:user.html.twig', array(/* variables */))
I want add other variables before rendering template.

You can override Symfony\Bundle\FrameworkBundle\Controller\Controller::render method in your controller and pass additional variables or arrange some events hooks there or in Twig.

Solution this problem:
Create a new Twig extension and register this extension.
Example:
Twig extension:
class UserExtension extends \Twig_Extension
{
/**
* Get globals variables
*
* #return array
*/
public function getGlobals()
{
return array(
// Other variables
);
}
}
Register extension as service.
<service id="sps.twig.user_extension" class="SPS\Bundles\HomeBundle\Twig\Extensions\UserExtension">
<tag name="twig.extension" />
</service>

Related

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

How can I render a controller action within a twig extension?

I do have a twig extension which has a method that uses another method from a different controller to render a json output via dependency jsonResponse.
How can I render a controller within a twig extension?
The following code below doesn't seem to work, because render() needs a view file instead of a controller. And I am now referencing to a controller.
class AreaExtension extends \Twig_Extension {
public function add()
{
$outputJson = $this->container->get('templating')->render(new ControllerReference('CMSCoreBundle:Area:index'));
}
}
$ref = new ControllerReference('CMSCoreBundle:Area:index');
$this->handler->render( $ref, 'inline', $options );
Where $this->handler is the fragment.handler service.
In your case:
$outputJson = $this->container->get('fragment.handler')->render(new ControllerReference('CMSCoreBundle:Area:index'));
You can find a full example in this symfony twig extension, see:
https://github.com/symfony/symfony/blob/4.1/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php#L28
and
https://github.com/symfony/symfony/blob/4.1/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php#L41

Global values: Define as service or define abstract Controller class?

In my Symfony2 app, I want to globally fetch a value from my database on each template and don't want to call on each Controller. I know I could define that as a service and inject that service into my twig templates (by defining it as a twig global).
Is that the common and recommended way? Or should I rather create an abstract Controller class where I fetch that value in my constructor and then inherit from all my other Controllers?
Note: It is actually not a static value that is the same for all users, but it is a user specific value which is different for each user.
If this variables are used to render the same spot on your page you can render an embedded controller. Like this:
<div id="sidebar">
{{ render(controller('YourBundle:User:stats')) }}
</div>
This will inject whole output of YourBundle/UserController/statsAction to the #sidebar div. Inside this action you can extract all inforamtion that you need.
If you need to use this variables in other way maybe you should look at response event.
Are you familiar with event listeners? http://symfony.com/doc/current/cookbook/service_container/event_listener.html
An event listener can be used to inject twig globals.
class ModelEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => array(
array('doProject', -1300),
),
KernelEvents::VIEW => array(
array('doView', -2100),
),
);
}
public function doProject(FilterControllerEvent $event)
{
$project = $whatever_is_needed_to_find_the_project();
if (!$project) throw new NotFoundHttpException('Project not found ' . $projectSearch);
// Add to request
$event->getRequest()->attributes->set('project',$project);
// Give all twig templates access to project
$twig = $this->container->get('twig');
$twig->addGlobal('project',$project);
}
# services.yml
cerad_core__model__event_listener:
class: '%cerad_core__model__event_listener__class%'
calls:
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
If it's a user value like you said you can get app.user.XXX on every twig template you need without processing nothing ;)

How I can don't use |raw filter in the template for services

I have a service, that generate and return simple html code of breadcrumb. But in template I always need to use |raw filter:
{{ $breadcrumb|raw }}
otherwise I can see escaped html code, not a real links:
Home / Contacts
With |raw filter it's work well, but maybe I do something wrong and there are any other ways to do this without |raw filter? Or it's a normally to use raw filter in this case? I think there is more suitable solution, is't it? help me better understand it, please.
Every argument passed to twig template is by default escaped. So it is normally that you need use raw filter. But if using raw filter is annoying for you, then you have two options.
First - disable autoescaping (Not recommended):
Turn autoescaping off globally by setting the autoescape option to false in config.yml:
twig:
autoescape: false
Second - Create twig extension (Recommended):
Create twig extension with function which will render content and it be html safe.
<?php
// src/Acme/DemoBundle/Twig/BreadcrumbExtension.php
namespace Acme\DemoBundle\Twig;
class BreadcrumbExtension extends \Twig_Extension
{
protected $breadcrumbService;
public function __construct(BreadcrumbService $breadcrumbService)
{
$this->breadcrumbService = $breadcrumbService;
}
/**
* Returns a list of functions to add to the existing list.
*
* #return array An array of functions
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('acme_breadcrumbs',
array($this, 'renderBreadcrumbs'),
array('is_safe' => array('html'))
),
);
}
public function renderBreadcrumbs()
{
return $this->breadcrumbService->renderHtml();
}
public function getName()
{
return 'breadcrumb_extension';
}
}
Register service:
<service id="acme.breadcrumb.twig.extension" class="Acme\DemoBundle\Twig\BreadcrumbExtension" public="false">
<tag name="twig.extension" />
<argument type="service" id="acme.breadcrumb.service" />
</service>
And now you can use it in twig template like this:
{{ acme_breadcrumbs() }}

How can I set the url for the form to POST using the FormBuilder in Symfony2?

I'm dynamically loading different form classes in my Controller and displaying them in my template. This works fine, except that the Symfony2 docs show adding the route for the form to POST to in the template by hand.
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>
I need to set that form action in the FormBuilder class-- the POST routes (e.g. 'task_new') are different depending on the form class I'm using. Is there a way to set the form action url in the FormBuilder class? How can we get {{ form_widget(form) }} to render the complete form, and not just the rows? Thanks!
It is possible out of the box -- http://symfony.com/doc/current/book/forms.html#changing-the-action-and-http-method
$form = $this->createFormBuilder($task)
->setAction($this->generateUrl('target_route'))
->setMethod('GET')
->add('task', 'text')
->add('dueDate', 'date')
->add('save', 'submit')
->getForm();
I had the same problem. I was using a simple FormType class and wanted to set the action url in buildForm function. I tried different things, but couldn't do it that way.
Eventually I used a Form option called 'action'. I don't think it's documented in the Symfony Reference, I have found it by accident while reading some error report :).
You can set the option when creating the form within your controller like this:
$form = $this->createForm(new FormType(), $obj, array( 'action' => 'whatever you want'));
It's not as pretty as having it encapsulated in the form class, but it works..
I hope this helps.
It's bad practice to change submit route in form type. It not form type responsibility. If you added form from not handle form route, you can just change action url in template:
{{ form_start(yourForm,{action:path('yourSubmitRoute')}) }}
I solved this problem by injecting the router into my form type. In my application I have created a zip code search form called ZipCodeSearchType:
Form Class
use Symfony\Component\Form\AbstractType;
/*
* I'm using version 2.6. At this time 2.7 has introduced a
* new method for the Option Resolver. Refer to the documentation
* if you are using a newer version of symfony.
*/
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Routing\Router;
/**
* Class ZipCodeSearchType is the form type used to search for plans. This form type
* is injected with the container service
*
* #package TA\PublicBundle\Form
*/
class ZipCodeSearchType extends AbstractType
{
/**
* #var Router
*/
private $router;
public function __construct(Router $router)
{
//Above I have a variable just waiting to be populated with the router service...
$this->router = $router;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('zipCode', 'text', [
'required' => true,
])
/*
* Here is where leverage the router's url generator
*/
//This form should always submit to the ****** page via GET
->setAction($this->router->generate('route_name'))
->setMethod("GET")
;
}
...
}
The next step is to configure your form as a service, and let symfony know that you need the router service injected into your class:
Define Form as Service
/*
* My service is defined in app/config/services.yml and you can also add this configuration
* to your /src/BundleDir/config/services.yml
*/
services:
############
#Form Types
############
vendor_namespace.zip_search_form:
class: VENDOR\BundleNameBundle\Form\ZipCodeSearchType
arguments: [#router]
tags:
- { name: form.type, alias: zip_code_search }
Use It In Your Controller
/**
* #param Request $request
* #return Form
*/
private function searchByZipAction(Request $request)
{
...
$zipForm = $this
->createForm('zip_code_search', $dataModel)
;
...
}
I don't think it's possible out-of-box today (Mar 18 '12). You could, however, do something like this:
in your controller:
....
....
$post_route = null;
if ( $something ){
$post_route = "some_post_route";
}else if ( $something_else ){
$post_route = "some_other_post_route"
}else{
$post_route = "my_default_route";
}
....
....
return array(
'post_route' => $post_route
);
... and in you template:
<form action="{ path(post_route) }" method="post" {{ form_enctype(form) }}>
Similar approach would be to generate URL (not just route name) within your controller and pass it to template, in which case you don't need path function there.

Resources