Make method of Twig-safe function also Twig-safe - php

I have defined a Twig function like this:
new Twig_SimpleFunction('link', 'html_link', ['is_safe' => ['html']]);
so every {{ link(...) }} isn't auto-escaped by Twig. That works.
But html_link() returns a Link object that can be tweaked in the template, to add a URL #fragment or link [attribute]:
{{ link('url', 'label').withFragment('foo') }}
{{ link('url', 'label').withClass('special') }}
But Twig detects that it's not a pure link() anymore, so it's not html-safe anymore, and I have to add |raw to make it work:
{{ link('url', 'label').withFragment('foo')|raw }}
{{ link('url', 'label').withClass('special')|raw }}
Which works. But. Not very pretty.
Can I tell Twig that link().withFragment() and link().withClass() are still html-safe?
Custom filters have an option preserves_safety that kinda works kinda like that:
{{ someFunction(someVar)|someFilter }}
If someFunfction is html-safe, and someFilter was defined with preserves_safety, the output is still safe. I don't want to make filters for ever Link method, so that's not quite good enough.
I can't use Twig_Markup, because:
It has to remain an object as long as possible (withFragment, withClass etc return $this), so you can do multiple things to it, e.g. add a class AND a fragment.
It must be usable outside of Twig. E-mails and flash messages can include links. Only Twig knows (and should know) what a Twig_Markup is. Since it's not an interface, I can't add it to the Link class.

Related

Making Twig filter to assign defaults unless otherwise specified

I'm trying to use twig as a template system for my website. I'm wanting to write something along the lines of...
{{ title }}
{% pageAuthor | Unknown %}
I would like "Unknown" to become the default value if pageAuthor is empty. I could use a bunch of "if" statements but that would hurt readability. This is clean and easy to write. Thanks for any help!
Use Twig's default filter:
{{ pageAuthor|default('Unknown') }}

Using locateResource inside Twig Extension

I wish to use the following inside of a Twig Extension
$kernel = $container->getService('kernel');
$path = $kernel->locateResource('#AdmeDemoBundle/path/to/file/Foo.png');
but this involved passing in the Kernel, which is bad. Plus I could not get it to work anyway when trying this method.
How can I access a resources path within a Twig Extension?
The Extension is already a Service. I can use Assetic to give me the URL, but I really want the path.
I had a similar need: i needed to pass to the filter the url of an image to display it in a for loop and build a string.
I passed the URL directly to the filter in this way:
{% image '#AppBundle/Resources/public/images/my_asset.png' %}
{% set resolved_asset_url = asset_url %}
{% endimage %}
{{ my_var|filter_name(resolved_asset_url)|raw(html) }}
In this way the Twig template resolve the correct resource's URL, sets it as a variable, and then I pass it to the filter from inside the template itself, without having to deal with kernel or something else.
If you want just to serve a download, you should create a Route that accomplishes that task.
In this way, you'll call the locator inside the route, and a simple
{{ path('route_that_does_the_locator_thing') }}
will be fine.
If you need instead to include a file in your template (ex. CSS, JS..), you need to declare your file as an asset, maybe using Assetic.

Why is passing of second variable on href much flexible in Laravel?

I am using Laravel Framework 4.1.30.
My Original Problem was...
I got a working route that when I type "localhost/user/alvin" it works but when I click a drop-down bar, it does not work.
drop-down bar from template was: (li tag removed)
{{ Auth::user()->username }}
route is:
Route::get('/user/{username}', array(
'as' => 'profile-user',
'uses' => 'ProfileController#user'));
View of URL when I click the dropdown bar is:
"localhost/user/%7Busername%7D"
I got 3 working answers for this from other communities.
First was a simple:
{{ Auth::user()->username }}
Second was:
{{ Auth::user()->username }}
Last was:
<a href="{{ URL::route('profile-user',['username'=>Auth::user()->username]) }}">
{{ Auth::user()->username}}</a>
Originally I thought the simplest (First Solution) form would be the wisest choice but as per most developer they choose the Third answer because it allows some flexibility to some extent.
I want to implement a good Quality Assured code as much as possible.
My question... Why would the 3rd solution be much more flexible compared to a much simpler code?
You can even use a more flexible, shorter modification of the third one:
<a href="{{ route('profile-user', Auth::user()->username) }}">
When you are using named routes, you do not put actual URLs, nor controller method names into your Views. You only provide a route name to your Views, and all other things are defined in your routes, and can be changed, if needed, without making breaking anything else. It would be a waste not to use this feature.
It is also more flexible, by not specifying the parameter name, so it can be changed in the routes too, without breaking the Views. This way they will be just taken in the order they were inputted. They are also read by a controller method in the order, i.e. public function index($parameter1, $parameter2) not by an associative array.
The route() is an alias to URL::route().

How to pass an array from Symfony 2 controller to a TWIG template ?

I can`t pass an array from a symfony 2 controller to a TWIG template. I use this code in the controller:
$searchTerms['color'] = "Red";
return $this->render('TestBundle::search.html.twig',
array(
"searchTerms" => $searchTerms));
In the twig template, I am trying to access the variable like this:
{{ searchTerms['color'] }}
{{ searchTerms.color }}
Both output nothing, empty string, so it seems like array comes to template but its elements are empty.
What`s wrong?
Yes, this code works. The first thing to check is that your twig code is in the correct page (TestBundle::search.html.twig). This might sound silly but that happens sometimes...
If this is all good, I suggest that you try to debug within your template. Debugging is the most important thing. You will always have this kind of problem while programming, especially when you try something new. The better you are at debugging your code, the better you are as a programmer because there is no way you can get everything right the first time.
So, how can you debug?
To debug within your twig template, you can use the debug extension of twig. To activate the debug option, you will have to do a quick change in your config file. You can also read this thread if your lost.
You can debug any variable within your template like this:
<pre>
{% debug searchTerms %}
</pre>
This way, you can easily debug your variable and test what your problem is:
{% debug searchTerms['color'] %}
If you want to debug things quickly, I highly recommend that you use the LadyBugBundle. It is an awesome tool that will allow you to do something like that:
In your controller:
ladybug_dump($searchTerms);
In your TWIG template:
{{ searchTerms|ladybug_dump }}
Not that different from a classic var_dump option, but if you have long arrays or objects, ladybug will impress you. More importantly, in a controller, you will often have the need to stop the code at a certain point to avoid the page to load after your debug statement, this is fairly easy with ladybug:
ladybug_dump_die($searchTerms);
You can even ask ladybug to load the "debugged" variable into Symfony profiler with this simple statement.
$this->get('ladybug')->log($searchTerms);
You have now direct access of the variable from a tab of the Symfony2 profiler.
Ladybug can do a lot more, but for this, the doc is really good.
I think you must change template like this:
{% for item in searchTerms %}
{{ item.color }}<br/>
{% endfor %}
See official documentation: Creating and using Templates->embedding-controllers

Twig - assume variables as object methods

This is a strange one. I'm trying to implement a 1:1 relationship between Twig and some ViewModel objects, such that Twig is aware of its context and assumes variables are methods on the object.
For example, I have a Twig template and a ViewModel_Product. I could do this...
$template->render(array('product', $product));
...and in the template...
<p>{{ product.name }}</p>
However, because the only thing that will ever be passed to the template is the model, it seems pointless to have users prefix each variable. Better usage would be:
$template->render(array('viewModel', $product));
...and...
<p>{{ name }}</p>
How can I achieve this?
I don't believe this would be possible because twig keeps track of other global variables in each template so how would it know if the variable {{ name }} is part of your view or some other global variable? And as it was mentioned above, having the variable prefix helps to namespace your view which makes for easier reading.
Don't be a lazy coder.

Categories