Slimphp - Twig dynamic include - php

I need to include template dynamically in twig template. So the template will included the page that defined in routes.php
I try to concatenate string and variable like this code bellow, but still not working.
routes.php:
$app->get('/home', function ($request, $response, $args) {
$data['page'] = "home";
return $this->view->render($response, 'Home/layout.html', $data);
});
Home/layout.html:
{% include 'Home/_header.html' %}
{% include 'Home/_topbar.html' %}
{% include 'Home/_sidebar.html' %}
{% include 'Home/' ~ data.page ~ '.html' %}
{% include 'Home/_footer.html' %}
Error message:
Unable to find template "Home/.html" (looked into: ../App/Templates) in "Home/layout.html" at line 4.
I found my own solution, but it's not the one i want. So I edited routes.php and layout.html like this:
routes.php:
$app->get('/home', function ($request, $response, $args) {
$data['page'] = "{% include 'Home/home.html' %}";
return $this->view->render($response, 'Home/layout.html', $data);
});
Home/layout.html:
{% include 'Home/_header.html' %}
{% include 'Home/_topbar.html' %}
{% include 'Home/_sidebar.html' %}
{% include template_from_string(page) %}
{% include 'Home/_footer.html' %}
What I want is to send variable that contain name of the template file from routes.php, not the template syntax instead.

You are accessing the wrong variable. You are passing the following to twig,
$data['page'] = "home";
return $this->view->render($response, 'Home/layout.html', $data);
Which means to access the variable page inside twig you would just need to call page and not data.page as the array data is not even passed towards to your template
Which means your template should look like the following
{% include 'Home/_header.html' %}
{% include 'Home/_topbar.html' %}
{% include 'Home/_sidebar.html' %}
{% include 'Home/' ~ page ~ '.html' %}
{% include 'Home/_footer.html' %}
You could actually tell this by yourself when looking at the error message
Unable to find template "Home/.html"
As there is nothing in between Home/ and .html, this mean the variable data.page doesn't exist. I recommend to enable debug mode when developing then u would get an error you are trying to access an undefined variable

Related

Translator component with twig trans

I am trying to have my twig templates translated using the Symfony Translator Component and .mo files. I used the i18n extention before but wanted a more reliable approach since locale handling for translations on Windows is a nightmare.
These class functions prepare the translation and template:
/**
* Constructor.
*
* #param string $template_dir
* #param string $locale
* #param string $locale_path
*/
public function __construct($template_dir, $locale, $locale_path)
{
$loader = new Twig_Loader_Filesystem($template_dir);
$this->parser = new TemplateNameParser();
$this->template = new \Twig_Environment($loader);
$this->translator = new Translator($locale);
$this->translator->addLoader('mo', new \Symfony\Component\Translation\Loader\MoFileLoader());
$this->translator->addResource('mo', $locale_path, $locale);
$this->template->addExtension(new TranslationExtension($this->translator));
}
/**
* Render template.
*/
public function render($name,$parameters=[]) {
return $this->template->loadTemplate($name,$parameters)->render();
}
Then i have this template:
<h1>{% trans 'Hello World!' %}</h1>
which throws this error:
Uncaught Twig_Error_Syntax: Unexpected token. Twig was looking for the
"with", "from", or "into" keyword.
Which i get because i am not adding the Twig_Extensions_Extension_I18n extension to twig environment. If i do that, the texts in trans functions are not translated because i am not using the filter as i should. For it to work i need to use the trans filter like so: {{ 'Some text'|trans }}.
Is there a way to make translation work with {% trans 'Some text' %} instead of {{ 'Some text'|trans }}? For example, can i add a custom trans function somewhere in the chain?
Note: I know that {% trans %}Some text{% endtrans %} works, but all my templates already use this syntax {% trans 'Some text' %}, and i would like to prevent having to rewrite everything.
The problem seems to stem from incompatible twig and symfony translator versions. But i am not sure tbh.
In my case, i solved the problem long-term by writing a simple script to replace the incorrect syntax with the correct one in each template file.
foreach (glob("your_template_path/*/*/*.twig") as $filename) {
$content = file_get_contents($filename);
$content = preg_replace_callback('/{% trans "[\s\S]+?" %}/',function($matches) {
$text = str_replace(['{% trans','%}','"'],'',$matches[0]);
return '{% trans %}'.trim($text).'{% endtrans %}';
},$content);
$content = preg_replace_callback('/{% trans \'[\s\S]+?\' %}/',function($matches) {
$text = str_replace(['{% trans','%}',"'"],'',$matches[0]);
return '{% trans %}'.trim($text).'{% endtrans %}';
},$content);
file_put_contents($filename,$content);
}
Maybe that helps someone.
Try this
"{% trans %}Hello World{% endtrans %}!"

{{ exception.message }} in Twig doesn't render HTML

I have this controller where \Exception is raised (I haven't figured out which SF2 Exception to use yet) upon certain condition. Here is it:
<?php
namespace My\AppBundle\Controller;
use ....
class MyController extends Controller
{
const EXCEPTION_MESSAGE = <<<EOF
My <b>HTML</b>
<br/>
<small>Small phrase</small>
EOF;
public function indexAction()
{
// my logic
if(in_array($data, $array))
throw new \Exception(self::EXCEPTION_MESSAGE);
// the rest of my logic
return ....
}
}
And in app/Resources/TwigBundle/views/Exception/error.html.twig
{% extends '::base.html.twig' %}
{% block body %}
<h2>Error</h2>
<p>{{ exception.message }}</p>
{% endblock %}
The problem is HTML is not rendered when seeing the error page in prod environement.
I tried {{ exception.message|raw }} and also setting autoescape to false as per this answer but it seems to have no effect.
How can I do to make HTML works when displaying the \Exception message in Twig?
Where ever in the code you catch the exception is where it is needed to be added to the array you pass to twig. for example
$vars = [];
try {
$a->indexAction();
//fill $vars
} catch (Exception $e) {
$vars['exception'] = $e;
}
//pass $vars to twig

How to programmatically set twig block content?

Is it possible to set the value of a twig template block from PHP code? I'm migrating from a different template engine and I need a bridge to set the value of blocks without using a twig template.
I've just got plain text that I was hoping to assign before I render the template.
If you want to include PHP files inside blocks, I suggest you to create an extension.
Sample
index.php
<?php
require(__DIR__ . '/../vendor/autoload.php');
$loader = new Twig_Loader_Filesystem('./');
$twig = new Twig_Environment($loader, array());
$function = new Twig_SimpleFunction('get_php_contents', function($file, $context) {
ob_start();
include($file); // $context is available in your php file
return ob_get_clean();
}, array('is_safe' => array('html')));
$twig->addFunction($function);
echo $twig->render('test.twig', array('name' => 'Alain'));
test.twig
{% extends 'base.twig' %}
{% block content %}
{{ get_php_contents('contents.php', _context) }}
{% endblock %}
base.twig
<html>
<div>I'm a base layout</div>
{% block content %}{% endblock %}
</html>
contents.php
<?php
echo '<div style="color:red">';
echo "Hello {$context['name']}, it is now: ";
echo date("Y-m-d H:i:s");
echo '</div>';
Result
<html>
<div>I'm a base layout</div>
<div style="color:red">Hello Alain, it is now: 2014-10-28 19:23:23</div>
</html>

Render a Twig expression over an HTML string

This could sound a bit weird, but this is a special situation:
I have an HTML string in $content, and I want to render a stylesheet block in the head tag of this content. This is what I have:
$response = new Response();
$response->setContent($content);
$response->headers->set('Content-Type', 'text/html');
// returns the HTTP headers followed by the content
return $response;
and I want to add:
{% stylesheets output='compiled/css/*.css' 'css/all.less' %}
<link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
Any idea of how could achieve it?
Edit:
As #CarlMarkham suggested, I tried:
$loader = new \Twig_Loader_Array(array(
'index.html' => $content."{% stylesheets output='compiled/css/*.css' 'css/all.less' %}
<link rel='stylesheet' href='{{ asset_url }}' />
{% endstylesheets %}",
));
$twig = new \Twig_Environment($loader);
return $twig->render('index.html');
but I got this error:
Unknown tag name "stylesheets" in index.html
Also tried this:
$loader = new \Twig_Loader_Array(array(
'index.html' => $content.'<link rel="stylesheet" href="{{ asset("compiled/css/*.css") }}">'
));
$twig = new \Twig_Environment($loader);
return $twig->render('index.html');
but I got:
The function "asset" does not exist in index.html
Take a look at Assetic. You should be able to build the html from within php (first code block click on the php tag). That example is for JS but it is the same concept for CSS. Now you have the string you can add it to the content.

Routing to embedded Modules/Bundles/Controllers in Symfony 2

I fully edited this post after doing research:
I'd like to realize a sidebar in the admin section which is integrated in every page, f.e. http://example.com/admin/index/:
class MyController extends Controller {
protected $modules = array();
public function __construct(){
$this->modules[] = "ModuleController:mainAction";
$this->modules[] = "OtherModuleController:mainAction";
}
public function indexAction(Request $request){
// do stuff here
return $this->render("MyBundle:My:index.html.twig",$data);
}
}
In the view should happen something like:
{% block modules %}
{% for module in modules %}
{% render module %}
{% endfor %}
{% endblock %}
So far so good, but these modules can contain forms which send post requests. I'd like to stay on the same page (http://example.com/admin/index/), so the action attribute of the form stays empty. The problem is: The post request will never be recognized by the Modules. So one idea was to hide a field in the according form that contains the name of the route, transform it to the according uri and send a sub request (in MyController):
public function indexAction(Request $request){
if($request->request->has('hidden_module_route')){
// collect all parameters via $request->request->keys()
$uri = $router->generate($request->request->get('hidden_module_route'), $parameters);
// 1: resolve the uri to the according controller and replace the target in the $this->modules array
// or 2: (after step 1)
$req = $request->create($uri, 'POST');
$subresponse = $this->get('kernel')->handle($req,HttpKernelInterface::SUB_REQUEST);
// inject the response into the modules and handle it in the view
}
[...]
}
That would work for me, but I'm not happy to have these responsibilities in the controller and it feels like there should be a better solution (one Idea is to register a kernel.controller listener that handles sub requests and injects the paths to the controller (which perhaps is marked via interface...)).
What do you think?
You could try to send the main request to your modules, so that they can bind the form with it.
{% block modules %}
{% for module in modules %}
{% render module with {'mainRequest': app.request} %}
{% endfor %}
{% endblock %}
And the module:
public function moduleAction(Request $request, Request $mainRequest)
{
$form = ...;
if ($mainRequest->isMethod('POST')) {
$form->bindRequest($mainRequest);
if ($form->isValid()) {
// You can have a beer, that worked
}
}
}

Categories