Where is code for `form_widget()` Twig function? - symfony

I went looking to see how Symfony's extended Twig function form_widget actually works. I was expecting to find the function in symfony / src / Symfony / Bridge / Twig / Extension / FormExtension.php . It's added to the function list there:
public function getFunctions()
{
return array(
...
new \Twig_SimpleFunction('form_widget', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))),
...
);
}
But no callable is listed (ie the 2nd arg is null). So what code is called when I use form_widget(a_form_element) in a Twig template?

My answer is a little late but I just happened to investigate the same question. This is what I found:
The code which is called when you use form_widget() is that produced directly by the SearchAndRenderBlockNode class when your template is compiled. The compiled template code then results in a call to Symfony\Component\Form\FormRenderer::searchAndRenderBlock() where the proper Twig block is located, provided with context, and rendered.

I don't recognize the name space there. Or that code.
Mine is in Symfony\Bridge\Twig\Extension\FormExtension.php
It uses Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode

You can find it in FrameworkBundle/Resources/views/Form/form_widget.html.php or it equivalent in template Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig.

This is now depreciated but here is available code directly using methods to render form when you call eg. form_row() twig function Here it is: https://github.com/symfony/symfony/blob/4.4/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php

Related

Symfony - render twig code in controller

I need to build a customizer for my customers. They will be able to choose between multiple templates for their subdomain.
In order to do that, I took the following path :
Store templates in the DB with twig tags for the user's data
When needing to display a preview of a template, I would get it from the DB
Then I would render it in the controller and send the resulting HTML as a variable to the main template
I'm not succeeding into rendering in the controller. I tried several things, but the closest I got is this :
$loader = new Twig_Loader_Array(array(
'code.html' => $site->getTheme()->getCode(),
));
$twig = new Twig_Environment($loader);
$code = $twig->render('code.html', array( "test" => "CUSTOM DATA" ));
But I miss a use statement :
Attempted to load class "Twig_Loader_Array" from namespace "AppBundle\Controller". Did you forget a "use" statement for another namespace?
I'm not sure if it's the right path though.
So, if it is, please help me find out what use statement to use. More generally, how do you find what use statement should be used ?
If it's not the right strategy according to you, please feel free to explain me how dumb my idea was :)
[EDIT] So thanks to #DarkBee I found this solution that works, but I'm not sure whether I should do this or not :
use Twig\Loader\ArrayLoader;
use Twig\Environment;
...
$loader = new ArrayLoader(array(
'code.html' => $site->getTheme()->getCode(),
));
$twig = new Environment($loader);
$code = $twig->render('code.html', array( "test" => "CUSTOM DATA" ));
So if it's ok, great. If it's wrong (or again, if I'm wrong in the strategy choices), please tell me why and what would be better.

Symfony 2 - Twig templates - Entity function results

I am looking for best solution how to send value returned by one of entity function's in symfony2 to twig template.
The problem is connecting with getting file url for file uploaded according to "How to Handle File Uploads with Doctrine" manual (http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html). I was following the last example ("Using the id as the Filename").
In controller I am getting one of documents entity.
$document = $this->getDoctrine()->getRepository('AppBundle:Documents')->find($id);
and I provide entity details to twig template:
return $this->render('AppBundle:Documents:details.html.twig', array('document' => $document));
However in the template I need to get link to the file which is generated by getAbsolutePath() function.
public function getAbsolutePath()
{
return null === $this->link
? null
: $this->getUploadRootDir().'/'.$this->id.'.'.$this->link;
}
I may use in controller the following code:
return $this->render('AppBundle:Documents:details.html.twig', array('document' => $document, 'link' => $document->getAbsolutePath()));
but this solution does not seems to tidy for me, as I am already sending $document to twig. What would be your practical solution?
Its simple. In a Twig template you can simply do:
{{ document.getAbsolutePath() }}

Return a value from entity to view file in symfony

I want to return a value from entity to view file. Below is my entity function
public function getVisitorName($id)
{
$repository = $this->getDoctrine()->getRepository('SystemVmsBundle:VisitorsDetails');
$product = $repository->findOneBy(array('id' =>$id));
$name=$product->getFirstname();
return $name;
}
This is the line in my view file which calls that function
{{ entity.visitorName(entity.visitorId) }}
Its not giving me any error. But only a blank page. How can i fix this?
This is my controller code
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('SystemVmsBundle:EntryDetails')->findAll();
return array(
'entities' => $entities,
);
}
I am trying to fetch the visitors name(from visitors table) corresponding to the visitor id(in entry table).How will i do it then?
you have two ways of doing it:
1) Map your SystemVmsBundle:EntryDetails entity, to SystemVmsBundle:VisitorsDetails as OntToOne by adding field details to your EntryDetails; , and then in twig template just call it via
{{ entity.details.name }}
2) instead of creating getVisitorName(), it is better to create twig function for this, with same functionality.
Your indexAction() is not returning a response object, it is just returning an array of entities. Controller actions should return a Response containing the html to be displayed (unless they are for e.g. ajax calls from javascript). If you are using twig templates you can use the controller render() method to create your response, something like this:
return $this->render('<YourBundle>:<YourViewsFolder>:<YourView>.html.twig', array(
'entities' => $entities,
));
When you've corrected that I suspect you'll get an error because $this->getDoctrine() won't work from an entity class. The code you have in the getVisitorName() method just shouldn't be in an entity class.
As #pomaxa has already suggested, I believe there should be a relationship between your EntryDetails and VisitorsDetails entities although I don't know enough about your data from the question to know what type of relationship it should be (OneToOne / ManyToOne). If your EntryDetails entity had a relationship to VisitorsDetails, the EntryDetails class would then contain a $visitorsDetails attribute and methods to get/set it. Then the line in your twig file would look like this:
{{ entity.visitorsDetails.firstName }}
There is a section on Entity Relationships / Associations in the Symfony Manual.
Also, I hope you don't mind me giving you a little advice:
Be careful when you copy and paste code as it appears you have done in getVisitorName(). You have kept the variable name '$product' although there are no products in your system. This sort of thing can cause bugs and make the code more difficult to maintain.
I recommend you avoid tacking 'Details' onto the end of entity names unless you genuinely have two separate and related entities such as Visitor + VisitorDetails and a good reason for doing so. I think the entities in your example are actually 'Visitor' and 'VistorEntry'.
Unless you are writing a re-usable component, I recommend you use specific variable names like '$visitorEntries' rather than '$entities' in your controller and twig.
In general, the more meaningful your variable names, the more readable, maintainable and bug-free your code is likely to be. Subsequently, it will also be much easier for people on SO to understand your code and give you help when you need it.

How to use custom Twig filters in Twig template rendered from string?

I want to render Twig template from string (in fact from database but it doesn't matters) and method showed in this question works fine. But I can't use my custom filters, defined in custom Twig extension, which works in templates rendered in standard way (from files).
What should I do to allow string-templates use custom filters?
I'm getting template from string in this way:
$env = new \Twig_Environment(new \Twig_Loader_String());
echo $env->render(
"Hello {{ name }}",
array("name" => "World")
);
I've found in \Twig_Environment class method addFilter but my filters are defined in Twig extension so I don't know if it's proper method to use.
Solution:
Thanks to Luceos comments I've solved my problem just by using existing Twig engine instead of creating new one. When I set Twig Environment this way:
$twig = clone $this->get('twig');
$twig->setLoader(new \Twig_Loader_String());
then everything works as expected. (Solution found in http://www.techpunch.co.uk/development/render-string-twig-template-symfony2)
Use Symfony's Twig instantiated object and do not set up your own. Setting up your own environment leaves out all added functionality by Symfony.
For instance use the render function in controllers: http://symfony.com/doc/current/book/templating.html#index-8
Or call on the template service directly:
http://symfony.com/doc/current/book/templating.html#index-12
use Symfony\Component\HttpFoundation\Response;
$engine = $this->container->get('templating');
$content = $engine->render('AcmeArticleBundle:Article:index.html.twig');
return $response = new Response($content);

Parse Smarty template without triggering calls to user plugins

I'm writing a (Symfony2) SmartyBundle extension to support Assetic.
To support the stylesheets feature I've registered a block plugin called stylesheets:
{stylesheets
assets='#SmartyBundle/Resources/public/css/*'
debug=true}
{$asset_url}
{/stylesheets}
This plugin gets called properly and everything work as expected when the Symfony/assetic cache is created.
The problem arises When the Symfony cache is empty and Assetic loads every template file resource and asks the template engine to retrieve a PHP array with the tokens found in the stylesheets tag. The class called to retrieve the array is SmartyFormulaLoader.
<?php
class SmartyFormulaLoader implements \Assetic\Factory\Loader\FormulaLoaderInterface
{
public function load(ResourceInterface $resource)
{
// raw template content
$content = $resource->getContent();
// a FileLoaderImportCircularReferenceException is throw here
$smartyParsed = $this->smarty->fetch('string: '.$content);
// build an array with tokens extracted from the block function
$formulae = $this->extractStylesheetsTokens($smartyParsed);
return $formulae;
}
When $smarty->fetch() is called in the load() method an exception is thrown: Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException: Circular reference detected in "." ("." > ".").
This is caused by the Smarty template being parsed/compiled and the stylesheets plugin being called again.
So I'm asking if Smarty provides a template parser that extracts the block function tokens (without calling the stylesheets plugin) so I can feed Assetic. Or any other solution that I may be missing to solve this.
Thanks.
After a little chat with Smarty dev #rodneyrehm we came to the conclusion that:
Should parse raw template source instead of compiling the template and parsing it
Should write my own parser (instead of using some Smarty internal helper)
So, for this particular case we came up with this implementation:
<?php
class SmartyFormulaLoader
{
public function load(ResourceInterface $resource)
{
// template source
$templateSource = $resource->getContent();
// ask Smarty which delimiters to use
$ldelim = $smarty->left_delimiter;
$rdelim = $smarty->right_delimiter;
$_ldelim = preg_quote($ldelim);
$_rdelim = preg_quote($rdelim);
// template block tags to look for
$tags = implode('|', array('stylesheets', 'javascripts', 'image));
// match every assetic tag
if (preg_match_all('#'.$_ldelim.'(?<type>'.$tags.').*?'.$_rdelim.'#s', $templateSource, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
// extract block attributes
if (preg_match_all('#(?<key>[a-zA-Z0-9_]+)\s*=\s*(["\']?)(?<value>[^\2]*?)\2(\s|'.$_rdelim.')#s', $match[0], $_matches, PREG_SET_ORDER)) {
$t = array(
'type' => $match['type'],
'attributes' => array(),
);
foreach ($_matches as $_match) {
if (empty($_match[2])) {
// make eval a little bit safer
preg_match('#[^\w|^\.]#', $_match['value'], $evalMatches);
$_match['value'] = ($evalMatches) ? null : eval(sprintf('return %s;', $_match['value']));
}
$t['attributes'][$_match['key']] = $_match['value'];
}
// call some Assetic methods with the extracted attributes
$formulae += $this->buildFormula($match['type'], $t['attributes']);
}
}
}
return $formulae;
}
}
Full implementation is available here: https://github.com/noiselabs/SmartyBundle/blob/master/Assetic/SmartyFormulaLoader.php

Resources