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.
Related
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.
Laravel 5.4 Blade introduced the concept of components & slots - but I can't see what they add over the traditional #include. As I understand, with component/slots, you do:
In template component-tpl.blade.php:
<div class='container'>
<h1>{{$slot1}}</h1>
<h2>{{$slot2}}</h2>
</div>
Using slots in page template, you do:
#component('component-tpl')
#slot('slot1')
The content of Slot 1
#endslot
#slot('slot2')
The content of Slot 2
#endslot
#endcomponent
What functionality does that provide over the older:
#include('component-tpl',['slot1'=>'The content of Slot 1',
'slot2'=>"The content of Slot 2"])
using the exact same 'component-tpl.blade.php' Blade template?
What am I missing? Thanks for any insights.
Chris
As stated, there's no functional difference I was incorrect - see benjaminhull's answer for details on variable scoping and passing blade syntax code. The following still holds for basic usage, though.
If a slot could contain HTML, then using a component will give a cleaner syntax in your blade files.
#component('test')
<strong>This text has html</strong>
#endcomponent
versus
#include('test', ['slot' => '<strong>This text has HTML</strong>'])
Equally, if a component has no slots, then an include may be preferred:
#include('test')
versus
#component('test')
#endcomponent
There are two key differences.
1. Variable scope
As described in #DavidHyogo's answer, a component only sees variables explicitly passed to it. So you have to give it all variables like so...
#component('my-component', ['foo' => 'bar', 'etc' => 'etc'])
Whereas an include will adopt all variables from the global/current scope by default - unless you define an explicit set of variables to pass it, which then becomes local scope again.
{{-- This include will see all variables from the global/current scope --}}
#include('my-component')
{{-- This include will only see the variables explicitly passed in --}}
#include('my-component', ['foo' => 'bar', 'etc' => 'etc'])
2. Component's {{ $slot }} vs include's {{ $var }}
When using a {{ $slot }} in a component, you can give it blade syntax code e.g...
{{-- alert.blade.php --}}
<div class="alert">{{ $slot }}</div>
#component('alert')
<div>Hello {{ $name }} #include('welcome-message')</div>
#endcomponent
Note how the slot will receive html AND blade syntax code and just deal with it.
This is not possible with includes because you can only pass variables into includes...
{{-- alert.blade.php --}}
<div class="alert">{{ $slot }}</div>
#include('alert', ['slot' => "I CAN'T PASS IN BLADE SYNTAX HERE!"])
It could be done in a more hacky way by grabbing a fresh view() helper and passing it some variables to compile the output we want to pass into the slot, but this is what components are for.
I think I've tracked down another crucial difference. For instance, from the documentation for 5.4:
Blade's #include directive allows you to include a Blade view from within another view. All variables that are available to the parent view will be made available to the included view:
As far as I can tell, components have a different scope from a containing view and so the variables available to the parent view are not available within the component. You need to pass a variable to a component like this:
#component('alert', ['foo' => 'bar'])
#endcomponent
This discussion is related to this problem:
Use variables inside the Markdown Mailables
As the documentation says:
Components and slots provide similar benefits to sections and
layouts; however, some may find the mental model of components and
slots easier to understand.
For me most important thing is component needs a class. So when I need just a simplest reusable part of html (blade) there is no need to create blade file + php file, just simple #include with subview is enough ;)
I've joined new project of website in Symfony3 framework and now I'm struggling an issue, trying to figure out this particular syntax in Twig:
<li>{{ 'site.template.menu.contact'|trans }}</li>
Arguments in path() twig function have name of route in my SiteController but i totally don't know what does code between <a/> tags, except 'trans' filter. I don't have any variables in my twig template file.
Have you seen something like this before? Where I should find information about this in docs or how to name syntax like this to find some information?
It is just the twig "internationalization" (often abbreviated i18n).
Docs for it are here.
The quotes around the object shouldn't be there. I'm assuming that an object called site is being passed to the view, so it should be {{ site.template.menu.contact|trans }}
To explain the dot notation in twig;
If your PHP array is something like;
$site['template']['menu']['contact'] = 'fubar';
If it is an object then it is just attributes of the object.
I'm attempting to do something that was very easy in PHP, but not so easy in twig.
Basically, I need to call a class method, but I need to be able to define the method to call via a string.
I have 3 methods: getControlvs, getControlnc and getControltr. However, in order to call these methods, I need a seperate variable which determines which one to call.
This is what I'm attempting to call right now:
{% set neutPer = key.getControl~neutFaction %}
Where neutFaction can be either "vs", "nc", or "tr".
This only seems to fire key.getControl and then that's it, the concatenation is lost.
Any ideas?
Thanks in advance!
You can use the attribute twig function:
First concatenate the string as:
{% set method = "getControl" ~ neutFaction %}
Then call as
{{ attribute(key, method) }}
You can pass arguments also as:
{{ attribute(key, method, arguments) }}
In addition, the defined test can check for the existence of a dynamic attribute:
{{ attribute(object, method) is defined ? 'Method exists' : 'Method does not exist' }}
Try {% set neutPer = (key.getControl~neutFaction) %}
Or you could change this in your controller/service that passes the variable to twig so that you do not need to setup a concatenation.
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