Symfony how to create reusable widget with PHP and twig - symfony

Let's say I have comments in more than one spot in website. How can I create something like
{{ render_widget('comments', {"object": object} ) }} ? That would render the form and list with all comments for that object ?

Create a service:
// src/Acme/HelloBundle/Service/Widget.php
namespace Acme\HelloBundle\Service;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Widget
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getComments()
{
$request = $this->container->get('request'); // use service_container to access request, doctrine, twig, etc...
}
}
Declare a service:
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
# ...
my_widget.class: Acme\HelloBundle\Service\Widget
services:
my_widget:
class: "%my_widget.class%"
arguments: ["#service_container"]
# scope: container can be omitted as it is the default
Use a service in controller:
namespace Acme\HelloBundle\Controller;
class BlogController {
public function getPostAction($id) {
// get post from db
$widget = $this->get('my_widget'); // get your widget in controller
$comments = $widget->getComments(); // do something with comments
return $this->render('AcmeHelloBundle:Blog:index.html.twig',
array('widget' => $widget) // pass widget to twig
);
}
}
or in twig, if you pass your service in template like above in render() function:
#AcmeHelloBundle:Blog:index.html.twig
{{ widget.getComments()|raw }}
And usefull to read the docs about How to work with Scopes

I have done it another way. I registered Twig Extension with function {{ comments(object) }}
The function is registered that way
'comments' => new \Twig_Function_Method($this, 'comments', array(
'needs_environment' => true,
'is_safe' => array('html')
))
That way I don't need to specify |raw filter.

Related

How to access Sonata admin pool in form type

Use case
I' m trying to reuse sonata_type_model_list in the front admin of my website by defining this in my buildForm method of my Entity FormType :
[...]
->add('position', 'sonata_type_model_list', array(
'model_manager' => $categoryManager,
))
[...]
However I can't use
$categoryAdmin = $this
->getConfigurationPool()
->getAdminByClass("\\Application\\Sonata\\ClassificationBundle\\Entity\\Category");
As I need to be in an AdminClass to use getConfigurationPool().
If anyone knows how to use getConfigurationPool() outside of an AdminClass or do you know how to declare sonata_type_model_list in order to use it outside of an admin class ?
You need to inject the admin pool in your form type.
Here is an example:
#services.yml
blast_base_entities.form.type.my:
class: Blast\BaseEntitiesBundle\Form\Type\MyformType
tags:
- { name: form.type, alias: blast_search_index_autocomplete }
arguments: [#sonata.admin.pool]
And in the form type:
use Sonata\AdminBundle\Admin\Pool
Class MyFormType
{
private $adminPool;
public function construct(Pool $adminPool)
{
$this->adminPool = $adminPool;
}
}
Then you can retrieve admins
$this->adminPool->getAdminByClass('Foo');
As writing in the doc, maybe you can use the sonata.admin.pool service.
$configurationPool = $this->container->get('sonata.admin.pool');
Solution
Thanks to #pbenard & #Mawcel for their answers, I combined them to achieve my goal as follow:
In my controller I create my form and inject $this->container->get('sonata.admin.pool') to it like so :
$form = $this->createForm(
new FormType($this->container->get('sonata.admin.pool')),
$entity,
$options
);
In my formType, I added the property $adminPool && the __construct method:
private $adminPool;
public function __construct(Pool $adminPool) {
$this->adminPool = $adminPool;
}
So I can now access the adminPool from the buildForm method:
public function buildForm(FormBuilderInterface $builder, array $options)
{
[...]
$categoryAdmin = $this
->getConfigurationPool()
->getAdminByClass("\\Application\\Sonata\\ClassificationBundle\\Entity\\Category" );
[...]
}

How do I check for the existence of a bundle in twig?

My application is made up with eight bundles, within my main layout I would like to check if a certain bundle exists so I can include a sub template, how do I go about doing this?
Thanks to #DonCallisto, I decided to make a twig function to use in my templates, the following is my twig extension.
<?php
namespace MG\AdminBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Bundles extends \Twig_Extension {
protected $container;
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
public function getFunctions()
{
return array(
new \Twig_SimpleFunction(
'bundleExists',
array($this, 'bundleExists')
),
);
}
public function bundleExists($bundle){
return array_key_exists(
$bundle,
$this->container->getParameter('kernel.bundles')
);
}
public function getName() {
return 'mg_admin_bundles';
}
}
I then registered it in my services.yml
services:
mg_admin.bundles.extension:
class: MG\AdminBundle\Twig\Bundles
arguments: [#service_container]
tags:
- { name: twig.extension }
Now in my twig templates I can check for registered bundles like this:
{% if bundleExists('MGEmailBundle') %}
{% include 'MGEmailBundle:SideBar:sidebar.html.twig' %}
{% endif %}
$this->container->getParameter('kernel.bundles');
will return all registered bundles (class names). You could pass that list - or parse it direclty - into a controller and pass it to your twig view
Then you should easily reach your target
If the bundle you want to check is a specific bundle, and you know the main class name, the easiest way may be:
if (class_exists('Acme\CommentBundle\AcmeCommentBundle'))
{
// Bundle exists and is loaded by AppKernel...
}

how to get currency symbol in twig, symfony2?

I am currently able to get currency symbol in symfony2 controller
$formatter = new \NumberFormatter($this->getRequest()->getLocale(),\NumberFormatter::CURRENCY);
$symbol = $formatter->getSymbol(\NumberFormatter::CURRENCY_SYMBOL);
and then pass it to twig.
However, because I need to get this currency symbol in many twig templates, inserting that piece of code in the corresponding controllers is not a pleasant thing to do. So, is there any better/easier way to do this directly in twig?
Thanks.
Here is how I create the custom twig function
namespace Acme\DemoBundle\Twig;
class AcmeExtension extends \Twig_Extension
{
public function getFunctions() {
return array(
'currencySymbol' => new \Twig_Function_Method($this, 'currencySymbolFunction'),
);
}
public function currencySymbolFunction($locale) {
$locale = $locale == null ? \Locale::getDefault() : $locale;
$formatter = new \NumberFormatter($locale, \NumberFormatter::CURRENCY);
$symbol = $formatter->getSymbol(\NumberFormatter::CURRENCY_SYMBOL);
return $symbol;
}
public function getName() {
return 'acme_extension';
}
}
The service:
acme.twig.acme_extension:
class: Acme\DemoBundle\Twig\AcmeExtension
tags:
- { name: twig.extension }
Because I need to get and pass the current defined locale in symfony2 parameters.ini into the twig function, I define a global twig value:
twig:
globals:
locale: %locale%
And finally in twig template:
{{ currencySymbol(locale) }}

Symfony 2 & Twig, how to access block from extension

I'm creating my Twig Extension to extend the actual "FormExtension".
Reason for that is that I need to create new functions without overwriting the current ones and making this available across my entire project.
So building and extension seemed to be the right way to go.
Building the extension is not a problem, my problem is how to render block from there?
What I understood till here, is that I need to create a Twig_Environment where I have to load my actual twig template (containing my blocks).
From there I should be able to render those block using "$mytemplate->displayBlock()".
Sample code:
public function renderWidgetinline(FormView $view, array $variables = array())
{
$loader = new \Twig_Loader_Filesystem(__DIR__.'/../Resources/views/Form');
$twig = new \Twig_Environment($loader);
$this->template = $twig->loadTemplate("form_layout.html.twig");
ob_start();
$this->template->displayBlock(???WHAT-PARAMS???);
$html = ob_get_clean();
return $html;
}
I found those information by looking at the Symfony base FormExtension.php file.
My questions are:
How does displayBlock() works, where can I found the defintion of that function?
Is what I described above the right way to go?
How should I proceed to have access to that new TWIG template together with the base form_div_layout.html template? Can I somehow get the current environment without having to recreated one and load my additional template there?
Thanks!
Have you tried to use renderBlock instead?
The first parameter you need is the name of the block, and the second should be an associative array of values passed to the block.
So what you would have in the case of a service that is rendering a block is the following:
The Service Class:
<?php
namespace Acme\BlockBundle\Blocks;
use Doctrine\Common\Persistence\ObjectManager;
Class Block {
private $om;
private $environment;
private $template;
public function __construct( ObjectManager $om, Twig $environment )
{
$this->om = $om;
$this->environment = $environment;
}
public function render( $template, $data )
{
$this->template = $this->environment->loadTemplate( $template );
// maybe query the DB via doctrine, that is why I have included $om
// in the service arguments
// example:
$entities = $om->getRepository( 'AcmePizzaBundle:Pizza' )->getMeatyOnes()
return $this->template->renderBlock( 'acme_block', array(
'data' => $entities,
));
}
}
The Twig Extension Class
<?php
namespace Acme\BlockBundle\Twig\Extension;
use Twig_Extension;
use Twig_Function_Method;
class BlockExtension extends Twig_Extension
{
protected $container;
public function __construct( $container )
{
$this->container = $container;
}
public function getName()
{
return 'block_extension';
}
public function getFunctions()
{
return array(
'render_block' => new Twig_Function_Method( $this, 'renderBlock', array(
'is_safe' => array( 'html' ),
)),
);
}
public function renderBlock( $template, $data )
{
return $this->container->get( 'acme.block' )->render( $template, $data );
}
}
The services.yml
parameters:
acme.blocks.block.class: Acme\BlocksBundle\Blocks\Block
acme.twig.block_extension.class: Acme\BlocksBundle\Twig\Extension\BlockExtension
services:
acme.blocks.block:
class: '%acme.blocks.block.class%'
arguments:
- '#doctrine.orm.entity_manager'
- '#twig'
acme.twig.block_extension:
class: %acme.twig.block_extension.class%
arguments:
- '#service_container'
tags:
- { name: twig.extension }
don't forget your template:
{% block acme_block %}
{% spaceless %}
{# do something with your data here #}
{% endspaceless %}
{% endblock acme_block %}
Then when you want to display it, you just need to call the twig function you have just created:
{{ render_block( '::block_template.html.twig', someDataOneThePage ) }}
By no mean this is a complete solution, but I have used something similar and it proved to be working.
HTH
Tam
[edit: April 2016 - for reference: this solution was working on a Symfony 2.4 project]

Symfony 2.0.3 Global template variable

I've got an Entity that I want to associate with the users session.
I created a service so that I could reach this info from where ever.
in the service i save the entities id in an session variable
and in the getEntity() method i get the session variable and with doctrine find the entity and return it.
this way to the template i should be able to call {{ myservice.myentity.myproperty }}
The problem is that myservice is used all over the place, and I don't want to have to get it in every since Action and append it to the view array.
Is there a way to make a service accessible from all views like the session {{ app.session }} ?
The solution
By creating a custom service i can get to that from where ever by using
$this->get('myservice');
this is all done by http://symfony.com/doc/current/book/service_container.html
But I'll give you some demo code.
The Service
This first snippet is the actual service
<?php
namespace MyBundle\AppBundle\Extensions;
use Symfony\Component\HttpFoundation\Session;
use Doctrine\ORM\EntityManager;
use MyBundle\AppBundle\Entity\Patient;
class AppState
{
protected $session;
protected $em;
function __construct(Session $session, EntityManager $em)
{
$this->session = $session;
$this->em = $em;
}
public function getPatient()
{
$id = $this->session->get('patient');
return isset($id) ? $em->getRepository('MyBundleStoreBundle:Patient')->find($id) : null;
}
}
Register it in you config.yml with something like this
services:
appstate:
class: MyBundle\AppBundle\Extensions\AppState
arguments: [#session, #doctrine.orm.entity_manager]
Now we can as I said before, get the service in our controllers with
$this->get('myservice');
But since this is a global service I didn't want to have to do this in every controller and every action
public function myAction()
{
$appstate = $this->get('appstate');
return array(
'appstate' => $appstate
);
}
so now we go create a Twig_Extension
Twig Extension
<?php
namespace MyBundle\AppBundle\Extensions;
use MyBundle\AppBundle\Extensions\AppState;
class AppStateExtension extends \Twig_Extension
{
protected $appState;
function __construct(AppState $appState) {
$this->appState = $appState;
}
public function getGlobals() {
return array(
'appstate' => $this->appState
);
}
public function getName()
{
return 'appstate';
}
}
By using dependency injection we now have the AppState Service that we created in the twig extension named appstate
Now we register that with the symfony (again inside the services section inside the config-file)
twig.extension.appstate:
class: MyBundle\AppBundle\Extensions\AppStateExtension
arguments: [#appstate]
tags:
- { name: twig.extension }
The important part being the "tags", since this is what symfony uses to find all twig extensions
We are now set to use our appstate in our twig templates by the variable name
{{ appstate.patient }}
or
{{ appstate.getPatient() }}
Awesome!
Maybe you can try this in your action ? : $this->container->get('templating')->addGlobal($name, $value)

Resources