How to render content from string/database with twig? [duplicate] - php

This question already has answers here:
What to use instead of Twig_Loader_String
(7 answers)
Closed 1 year ago.
Because of several reasons including translation of content I had to build a simple CMS to render the pages of my Symfony2 application.
My problem is that is seems impossible to render content from a string. Twig only accepts files. My content may contain dynamic parts like locale or similar, so the render power of twig would be very useful.
I tried to render it using the TwibstringBundle, but its functionality is quite limited and it does not work with the path-function.

see http://twig.sensiolabs.org/doc/functions/template_from_string.html
and http://symfony.com/doc/current/cookbook/templating/twig_extension.html#register-an-extension-as-a-service
{% include template_from_string("Hello {{ name }}") %}
{% include template_from_string(page.template) %}
Since the string loader is not loaded by default, you need to add it to your config.
# src/Acme/DemoBundle/Resources/config/services.yml
acme.twig.extension.loader:
class: Twig_Extension_StringLoader
tags:
- { name: 'twig.extension' }
Where Acme/acme is your application name and DemoBundle is the bundle you want to enable it for.

Symfony 2.7 makes this easy from PHP, too:
$twig = $this->get('twig');
$template = twig_template_from_string($twig, 'Hello, {{ name }}');
$output = $template->render(['name' => 'Bob']));

twig_template_from_string function's source code is as follows:
function twig_template_from_string(Twig_Environment $env, $template)
{
return $env->createTemplate($template);
}
It means that if you already have a twig environment then it's better to call directly:
$template = $env->createTemplate($templateString);
$parsedContent = $template->render(array('a'=>'b'));

Related

Can twig macros return values?

I'm trying to write a template in Twig. Inside it I would like it to perform some manipulations on the string data that comes from the controller. In particular, there's a common manipulation (convert from underscore_case to CamelCase) that I'd like to bring out into a separate function. Then later I can use it repeatedly like {% set x = magic(a) ~ magic(b) %}. However I cannot find how to make such a reusable function inside the template itself. There are macros, but those don't seem to be able to return values. Filters are another option that seem to fit the bill, but I can only define those on PHP side.
Can this be done? Or should I do all the advanced string manipulation controller-side? It kinda feels like I'm pulling parts of display logic in there; things that should be in the view.
Twig is for outputting data. If you need to "transform" the data you need to do that before you send it to twig or you need to extend twig
Ideally, all the data you send to twig is just variables and arrays that needs the least amount of manipulation on their own.
When you're actually "in" twig, the data processing can be assumed to be "done" and only needs to be outputted in the appropriate places with minimal logic to decide user interface styles.
So revisit your logic and prepare your data better before sending it to twig.
An example for extending a toolkit class that contains our magic methods to do real wizardry.
class CustomToolkit
{
public function magic_a($a)
{
return strtolower($a); }
public function magic_b($b)
{
return camel_case($b);
}
public function magic_tidle($a, $b)
{
return $this->magic_a($a) ~ $this->magic_b($b);
}
}
Then you add this to your twig instance. I added here a complete instantiation loop. if you have a service provider you can just grab the instance from there and add it to that one.
$twig = new Twig_Environment(new Twig_Loader_Array([
'html' => $contents
]),[
'auto_reload' => true,
'debug' => false,
]);
$twig->addExtension('toolkit', new CustomToolkit ());
echo $twig->render('html', $values);
Then in your twig code you should be able to do something along the lines of
{% set x = toolkit.magic_tidle("value","value_b") %}
You are right, macros do not have return values and you cannot really make them have any. All they do is outputting strings.
Still, you are able to capture string output using set: https://twig.symfony.com/doc/2.x/tags/set.html
The syntax looks similar to this:
{% set var %}
{{ call.macro() }}
{% endset %}
The output of the macro call is then stored inside var. You may want to strip the whitespace though.
But then, consider rethinking what you are doing. Is this still presentation logic, or is your controller simply "too lazy" to transform the strings prior to passing them to twig? If it's really presentation logic, simply adding a twig filter by extending twig surely is worth the hassle. Not only because your code becomes testable.
Can this be done?
Yes! However, Twig templates are not the ideal places to run logic. At least, we should do our best to avoid it. Instead, Controller should return what Twig template needs. Logic should run in Service, Utility, Helper (you name it) etc. and Controller returns it to Twig. Twig then just displays it.
Can twig macros return values?
Yes! Look at this example. It accepts parameters and returns (not a real "return" thing but you get the idea) something back.
Example:
Assuming that the data you are trying to manipulate is something simple.
use Doctrine\Common\Inflector\Inflector;
Controller
{
action() {
$data = Inflector::camelize('hello_world'); // This will become helloWorld
return ....;
}
}
Look into Inflector class. It has useful stuff.

Is there a difference in using .html.twig vs .twig

What is the difference between using .html.twig and .twig? Is there some kind of standard, is it framework specific, or is it just user preference?
Under Symfony you have the possibility to deliver different formats automatically. So you can create files like test.json.twig, test.xml.twig for example. If you define all that extensions in your controller you can deliver all that formats under one action.
For example:
/**
* #Route("/hello/{name}.{_format}", defaults={"_format"="html"}, name="_demo_hello")
* #Template()
*/
public function helloAction($name) {
return array('name' => $name);
}
Something like this. So you can use the format in your route to define the response format.
According to Symfony Documentation
"this is simply an organizational tactic used in case the same resource needs to be rendered as HTML (index.html.twig), XML (index.xml.twig), or any other format."
So sounds like this is user preference, but a good standard to follow either way.
The only difference is the behavior of auto escaping.
Imagine you have a var variable containing: <div>I'm happy</div>.
On index.twig, {{ var }} will render <div>I'm happy</div>.
On index.html.twig, {{ var }} will render <div>I'm happy<div>
On index.js.twig, {{ var }} will render \x3Cdiv\x3EI\x27m\x20happy\x3Cdiv\x3E
And so on.
Always use the right extension to avoid any XSS vulnerability, and always use |raw wisely because it overlaps this extension's implicit protection.

Making a custom latex escaper for twig

I have a twig template than generates a Latex file. It works fine except that it issues characters escaped for html.
So for instance, the DB returns the string Rosie & Jim.
Twig makes that Rosie & Jim.
For latex I need the & to actually render as \&.
Therefore I think I need a custom escaper for Latex as mentioned here: https://twig.sensiolabs.org/doc/2.x/filters/escape.html#custom-escapers
However, I'm a little green when it comes to twig and can't find any examples of actual escapers anywhere. Does anyone know:
a) Is this what I actually need?
b) What I actually need to do to make one?
Right so I ended up setting it as a service and calling it from the controller as and when it's needed.
Not sure if it's best practice but it works for me.
The service:
<?php
namespace AppBundle\FunBundle\Service;
class Latex
{
public function __construct($twig)
{
$this->twig = $twig;
}
public function escaper()
{
// Get twig env
$twig = $this->twig;
// Start setEscaper called 'latex' - call it what you want
$twig->getExtension('Twig_Extension_Core')->setEscaper('latex',
function($twig, $string, $charset){
// Replace every instance of '&' with '\&'
$string = str_replace("&", "\\&", $string);
}
return $string;
);
}
}
Then in app/config/services.yml:
services:
app.latex:
class: AppBundle\FunBundle\Service\Latex
arguments: ['#twig']
Then in a controller action:
$latexer = $this->get('app.latex');
$latexer->escaper();
And in the twig template itself:
{% autoescape 'latex' %}
# latex/twig goes here
{% end autoescape %}
Ended up writing it up here on my site, which includes a full example of a LaTeX escaper.

Evaluate twig functions in record

I've created a bolt extension which provides a new twig function foo. The twig functions is added to the twig framework with the following code $this->addTwigFunction('foo', 'twigFoo');.
public function twigFoo()
{
$markup = '
<hr>
Foo
<hr>';
return new \Twig_Markup($markup, 'UTF-8');
}
My idea was that the users of the cms can use the twig function in the content types. But when the body of a record is displayed the twig function is visible as plain HTML for example: {{ foo }}
I think the problem is, that the twig template will be rendered before the record body will be assigned. So the body of my record will not be evaluated by twig. Has anyone a idea how to evaluate the twig function witch is use in a record? What's the best practice for this problems?
The field in the ContentType needs allowtwig: true to tell Bolt that you trust the field/editor to allow this, e.g.:
body:
type: html
allowtwig: true
The Problem is that Twig does not render Twig inside a Twig variable. You could create an escape function to still do that. Anyway this might not be the best idea to give your CMS users the possibility to use Twig as this gives them full access to your code.
Anyway, here is an escape function that could help you
$this->app['twig']->getExtension('core')->setEscaper('code', function($twigEnv, $string, $charset) {
$twig = clone $this->app['twig'];
$twig->setLoader(new \Twig_Loader_String());
return $twig->render($string);
});
You could then use the twig filter "code" in your template. e.g.:
{{ record.body|escape('code') }}

Does Twig support this?

I'm playing around with a CMS idea for Symfony and I'm not sure if what I want to implement is possible. I'm looking for some guidance.
I want to create a twig function that acts more like a block.
I want to be able to write template like this:
{% content('main_content') %}
<p>Some markup here</p>
{% end content('main_content') %}
where content($id) is a function that gives me access to the markup inside and allows my twig extension to change that markup if needed.
The content($id) function's goal will be to change the mark up based on what $id is given.
Obviously my train of thought on this implementation is off because I cannot find any docs relevant to this.
I'm not sure how to word this question : Is it possible to write a twig function that acts like a block and gives me access to the inner data. If possible is there some examples or blogs you can link me to? I'm using Symfony so answers with Symfony implementations are a plus.
edit: an example of the content($id) function
class TwigExtension extends \Twig_Extension{
public function content ($id, $content) {
/* pseudo code now*/
if($id = "some condition"){
return $someNewContent;
}
return $content;
}
}
You need to create a new Twig tag. More documentation here: https://github.com/twigphp/Twig/blob/master/doc/advanced.rst#tags
Also the Twig_Node_Block will give a good idea of what you need to build:
https://github.com/twigphp/Twig/blob/1.x/lib/Twig/Node/Block.php

Categories